第三方接入平台需求建模

对照着需求文档我们对平台进行建模

首先第一点关注到的是关键词

  1. 应用
  2. 授权
  3. 审核
  4. 开发者
  5. 企业

所以创建以下几个对象:

关注到授权是个动作,我们将auth的动作添加到几个对象上去,注意到几个auth的对象不一样,我们需要做区分,按传参区分

此时我们动态的再脑中运行一下,发现这个模型只满足了授权的需要,我们把其他需求添加进来

现在就差Application的状态跃迁由谁来负责了

按照逻辑上来讲,开发者应该知道应用开发的整个生命流程,但是由于这是在第三方平台上,有一些不可见的逻辑,我们这时候应该把Application的状态职责由Application自己负责管理所以如下:

这个时候发现,Developer和Application都自己负责了自己的生命周期的进行,所有操作只是发起了一个流程。

另外,关注到,审核的信息其实是Application的一个Snapshot而已,所以无论外界对Snapshot做了什么都不会影响到Application本身,但是要注意到,Snapshot本来不应该可以被修改,故而要有final修饰。

这时候核心的几个概念就已经可以用这个类图来表达了。

接下来就是扩展Application得到需求所描述的模型了,其中以下几个需求完全是基于上述模型可以直接扩展:

  • 有的应用需要把代码托管到平台处
  • 有的应用还需要数据库应用
  • 有的应用不需要审核只需要用户授权即可直接添加到用户的列表中

最终得到以下类图

第三方应用接入平台需求

第三方开发者接入平台需求文档

  • 开发者要先认证才能提交应用
  • 开发者认证需要提交一系列材料
  • 应用的信息能够多次修改
  • 应用的信息应该多次修改后能够被检索
  • 应用需要被审核才能被上架
  • 应用含有如下信息:
    • 应用的名称
    • 应用的图标
    • 应用的分类
    • 应用的授权码/签名
    • 应用的说明
  • 要区分已审核应用和未审核应用
  • 有的应用需要把代码托管到平台处
  • 有的应用还需要数据库应用
  • 有的应用不需要审核只需要用户授权即可直接添加到用户的列表中
  • 开发者创建一个应用时候默认需要对开发者本身做授权
  • 企业用户只有在付款并且应用成功开通之后才进行授权

如何问问题

Q:为什么提出这个问题

A:每次面试的时候总是面试官提问题,应聘人回答,但是回答完之后却又发现其实觉得自己没有真正回答任何一个问题,我想通过这个问题,复盘一下我面试时候出现的问题。

Q:所以应该怎么问问题呢?

A:首先先区分这个是不是有效问题,

怎么定义有效问题这个词语呢,我举个简单的例子:

如何对10W的数据进行排序;(这是一个无效的问题)

如何对10W的冷数据进行排序;(这个开始缩小范围了)

如何对10W的冷数据在获取的时候排序(继续缩小范围)

如何对10W的冷数据在获取的时候得到前100位的数据;(范围继续缩小)

如何对10W的冷数据在获取的时候按照数据存放的时间获取最近的100条(这算一个有效问题了)

所以,我们如何定义一个问题是有效问题,即是:能够很清晰简单的回答的问题,比如对10W的数据排序一上来提问,是个人都会是懵的,除非他在日常工作中曾经遇到过相同情景的事情,那他回答的一定会是他的那个分支的问题。

Q:那我们遇到面试官提问的时候如何能够得到真正的问题

A:像平时和产品或者需求提出者那样交流。首先我们应当根据问题确定提问的上下文,比如,还是以上面排序的例子作为示范:

我和产品或者BA会这么沟通:

  • 我要对这10W条数据倒序输出
  • 你这是什么样的使用场景呢
  • 这样的,我需要展示用户的最近几条操作记录以供恢复账号使用,是给运营人员用的
  • 哦,那事实上你只需要最近的操作记录是吧?你大约需要展示多少条呢?
  • 100条
  • 那就是按照时间排序,把最近的100条操作记录返回给页面是吧?
  • 这些数据在业务上来说用户是无法串改的把?
  • 运营方也不许篡改啊
  • 也就是这是这些数据其实是固化了的?
  • 那整个就变成了,我要从10W条固化数据提取出按照操作时间得到最近100条操作一记录并返回给运营人员(得到完整需求)

那么,作为开发,我就会选择在存储的时候用一个Buket存储最近100条数据,然后多个Buket组成一条直链并按照时间严格做排序

查询或者取数据的时候,则只要沿着buket一直扫描或者直接由时间计算得到位置即可。

Q:但有时候提的问题例如:介绍一下SpringMVC是如何工作的

A:这个其实并没有在问问题,这是在考察背书能力毕竟清楚SpringMVC是如何工作的表明,我应该是这个领域的专家。而事实上大部分工作的码农依赖的是HTTP协议中的一些特定部分而非SpringMVC这么一个特定框架下的领域知识。遇到这样的面试或者说面试过程中大部分都是这种问题的话,要考虑下是否还要去这样的公司,毕竟能问出这种问题的公司要么整天是修业务BUG,要么是对SpringMVC这种框架做过hacker使用的公司,哪种日子都不会好过。

Q:所以面试前应该有什么准备工作呢?

A:面试前其实没什么准备工作,面试最主要的是让求职者和公司之间进行交流。不要把面试想象的太过于像考试,而不是交流,心态变化了,那么行动上就会自然的出现变化。

前K项问题

其实吧,这个问题问出来是没有答案的,因为在实际业务中会有很多种使用场景,我们需要按场景来分类才能得到真正的问题

必须要服务端全局有序的

数据量小的时候,直接快排

数据量较大时候,对数据做分段,然后归并排序

不需要服务端全局有序的

数据直接交付client处理排序

而client有区分是否是强力PC使用还是弱计算能力的计算设备

 

除了以上,还要区分,要求一直有序还是说,最后输出的时候有序,还是说其他需求

以面试时候提到的跳一跳排名来说,其实根本没有服务端排序这个事情,小程序获取成绩应该是三个步骤:

  1. 获取用户列表中已开通跳一跳的用户
  2. 获取步骤1中所有user的成绩
  3. 对成绩按大小倒序排

就会发现和前K大这个问题没有任何关联。。。

总的来说。。我发现面试的数据结构题目对于强人和应试型人才很有优势。。but,我这种经常是时候脑袋一转发现不对的人来说是很难的。。。毕竟我的注意力也不在这里面。。

假装读懂Java线程池

假装读懂Java线程池

线程池是干什么的

不用线程池则是一个任务对应一个线程,若采用线程池那么,固定数量线程可以完成超过线程池的任务数。

为什么要用线程池

每个Thread执行的任务时间可长可短可能死循环,我们假设一万个任务需要执行,其中有几个会死循环,那么可以断言,会产生内存泄漏因为线程无法释放。

如果采用线程池,第一可以减少上下文的切换提升效率,第二就算产生泄漏,也能控制损害范围不至于是整个系统崩溃

Jdk是如何实现的

Jdk将任务和线程的角色分离了,任务称为Task是一个Runnable对象(应该是为了无痛切换原有用Thread自己实现的run,不然单独建立一个Task对象会与存量代码产生割裂),完成Task的对象成为Worker,Worker就是实际工作的线程,注意这里的Task可以为任意数目,而Worker只能是有限数目。

具体流程是:

1.用户submit或者execute提交一个Task

2.查找/创建Worker并检查是否有空闲

3.将任务赋予Worker并改变Worker当前状态,增加WorkCount

4.Worker一旦将任务执行完毕且任务队列为空将当前Worker丢弃,并减少WorkCount

5.检查当前线程池是否满足最小启动线程值

6.阻塞在获取Task状态直到下一个任务队列不为空,重复流程2

当然还有其他一些细枝末节的,就不再这个主流程上展示了

为毛不画流程图

大哥。。我只能手绘。。工具不会用

如何避免将写过程式代码

如何避免将写过程式代码

这是我在写理才网的应用管理平台时候获取的教训。

我们先来看一个简单的应用提交的模型:

其中类Application是单纯的数据类,所有的业务都集中在了ApplicationService,如果这个程序没有迭代的需求当然这是一个很好很简洁的代码,但是一旦我们增添一个需求,比如我的应用是有多种类型的,某些类型做一些操作,另一些又不用,还有一部分公用的操作,我们看看上图的简单模型会变成如何:

这个时候我们会往Service的具体方法做如下处理:

If(application.getApplicationType()==type1){

//dosomething

}else if(){

//dosomething

}………………

只是增加了一个需求,代码就开始变得臃肿起来

另一种做法是什么呢我们来看看:

这种做法就是将不同类型的Application建立不同的数据类,每个数据类的Service方法都不同,这样也可以做到简单,但是不简洁,Service的膨胀依旧存在,而且比原来更加突出。最后就会变为,每种Application的类型都有一个对应的ApplicationTypeNService。不知道在阅读本文的各位有没有想过如何用C语言写出面向对象的代码。实际上就是如同最后一种方法所说,每个方法的第一个入参是一个结构体指针。

既然我们都已经采用了Java这样的面向对象语言,为何不直接利用起来呢。

我们看最后面的这个方法如何改造:

这个时候我们把公共的一些方法放在了基类Application中,不同类型的Application的特殊操作放在了其实现类中,相对于前两种方式就十分的简洁,并且实现种也极大的减少了if-else语句

自用动态dns(Server/Client)

断断续续的做了一个月(主要是懒)

完成了

Server负责接受并返回Client的公网IP

Client负责定时轮询Server并在IP更新至阿里的DNS服务器

Client

Sever

在client和Sever,go build main.go

client的启动参数分别为 accessKey accessId severAccessKey severIP:port

server启动参数为

severAccessKey

下一步将domain和RR放在配置文件中

重构是什么

重构是什么

一般意义上来说,程序的重构和文章的修辞变更有着相同点

  1. 都是对同一件事情的描述
  2. 表达方式不尽相同

比如,购买这个操作:

伪代码可以写成:

Def buy:

{

Do transaction:

Service.Good -1

Customer.Good+1

Service.money+

Customer.money-1

Transaction end

}

也可以写成:

Def buy{

Do transaction

Service.Good-1

Customer.Good+1

End transaction

Defer:

Transfer money

}

两段伪代码都描述了这么一件事情

顾客购买商品的操作抽象为:

顾客拥有+1

商户拥有-1

顾客金钱-1

商户金钱+1

第二种写法在一般的做法上会比第一种写法更加灵活,因为我们将不同阶段的事情分开了,转账和库存变成了两个事情,可以异步进行

但是如果我们要求不高,第一种写法也是绝对可以满足需要的

这就是重构。

所谓代码的重构无非也就是将一开始语义不显著的代码,变为语义显著的代码,减少代码之间的不确定性,优化代码描述事件的模型。所谓clean code更多的是表现在能够准确的反映业务模型的操作。

实现hashMap

第一版:

package com.michaelssss;

import java.util.ArrayList;
import java.util.List;

/**
 * @author michaelssss
 * @since 2017/12/7
 */
public class SimpleMap<T, P> {
    private List<Pair<T, P>>[] buckets;

    public SimpleMap() {
        buckets = new ArrayList[64];
    }

    public void put(T key, P value) {
        int hashCode = key.hashCode();
        if (buckets[(hashCode % 64)] == null || buckets[(hashCode % 64)].isEmpty()) {
            buckets[(hashCode % 64)] = new ArrayList<>();
            buckets[(hashCode % 64)].add(new Pair<>(key, value));
        } else {
            boolean found = false;
            for (Pair<T, P> pair : buckets[(hashCode % 64)]) {
                if (pair.key.equals(key)) {
                    pair.value = value;
                    found = true;
                }
            }
            if (!found) {
                buckets[(hashCode % 64)].add(new Pair<>(key, value));
            }
        }
    }

    public P get(T t) {
        P value = null;
        int hashCode = t.hashCode();
        if (null == buckets[(hashCode % 64)] || buckets[(hashCode % 64)].isEmpty()) {
            return null;
        } else {
            for (Pair<T, P> pair : buckets[(hashCode % 64)]) {
                if (pair.key.equals(t)) {
                    value = pair.value;
                    break;
                }
            }
        }
        return value;
    }

    private static class Pair<T, P> {
        T key;
        P value;

        Pair(T key, P value) {
            this.key = key;
            this.value = value;
        }
    }
}

 

 

第二版(加入resize操作):

package com.michaelssss;

import java.util.ArrayList;
import java.util.List;

/**
 * @author michaelssss
 * @since 2017/12/7
 */
public class SimpleMap<T, P> {
    private List<Pair<T, P>>[] buckets;
    private float loadfactory = 0.75f;
    private int bucketSize;
    private int count;

    public SimpleMap() {
        bucketSize = 64;
        buckets = new ArrayList[64];
    }

    public SimpleMap(int initSize) {
        bucketSize = initSize;
        buckets = new ArrayList[initSize];
    }

    public SimpleMap(int initSize, float loadfactory) {
        this(initSize);
        this.loadfactory = loadfactory;
    }

    public void put(T key, P value) {
        int hashCode = key.hashCode();
        if (buckets[(hashCode % bucketSize)] == null || buckets[(hashCode % bucketSize)].isEmpty()) {
            buckets[(hashCode % bucketSize)] = new ArrayList<>();
            buckets[(hashCode % bucketSize)].add(new Pair<>(key, value));
            count++;
        } else {
            boolean found = false;
            for (Pair<T, P> pair : buckets[(hashCode % bucketSize)]) {
                if (pair.key.equals(key)) {
                    pair.value = value;
                    found = true;
                }
            }
            if (!found) {
                buckets[(hashCode % bucketSize)].add(new Pair<>(key, value));
                count++;
            }
        }
        if (loadfactory * count > bucketSize) {
            resize();
        }
    }

    private void resize() {
        int newBucketSeize = bucketSize * 2;
        List<Pair<T, P>>[] buckets = new ArrayList[newBucketSeize];
        for (List<Pair<T, P>> bucket : this.buckets) {
            if (null != bucket) {
                for (Pair<T, P> pair : bucket) {
                    int hashCode = pair.key.hashCode();
                    if (null == buckets[hashCode % newBucketSeize]) {
                        buckets[hashCode % newBucketSeize] = new ArrayList<>();
                    }
                    buckets[hashCode % newBucketSeize].add(pair);
                }
            }
        }
        this.buckets = buckets;
        this.bucketSize = newBucketSeize;
    }

    public P get(T t) {
        P value = null;
        int hashCode = t.hashCode();
        if (null == buckets[(hashCode % bucketSize)] || buckets[(hashCode % bucketSize)].isEmpty()) {
            return null;
        } else {
            for (Pair<T, P> pair : buckets[(hashCode % bucketSize)]) {
                if (pair.key.equals(t)) {
                    value = pair.value;
                    break;
                }
            }
        }
        return value;
    }

    private final static class Pair<T, P> {
        T key;
        P value;

        Pair(T key, P value) {
            this.key = key;
            this.value = value;
        }
    }
}