代码大全

前期准备

前期准备的重要性

不要立即开始写代码,要做好必要的需求分析和架构设计,写好需求文档和技术文档,防止浪费时间和精力制造错误的东西

需求核对表

针对功能需求:

1.是否详细定义了系统的全部输入,包括其来源、精度、取值范围、出现频率等?

2.是否详细定义了系统的全部输出,包括其目的地、精度、取值范围、出现频率格式等?

3.是否详细定义了所有的输出格式(如:web页面、报表等)?

4.是否详细定义了所有硬件及软件的外部接口?

5.是否详细定义了全部外部通信接口,包括握手协议、纠错协议、通信协议等?

6.是否列出了用户所要做的全部事情?

7.是否详细定义了每个任务所用数据,以及每个任务得到的数据

针对非功能需求(质量需求)

1.是否为全部必要的操作,从用户的角度,详细描述的期望的响应时间 ?

2.是否详细描述了其他与计时有关的考虑,如处理时间、数据传输率、系统吞吐量等?

3.是否详细定义了安全级别

4.是否详细定义了可靠性,包括软件失灵的后果、发生故障时需要保护的至关重要的信息、错误检查与回复的策略等?

5.是否详细定义了机器内存和剩余硬盘空间最小值?

6.是否详细定义了系统的可维护性,包括适应特定功能的变更、操作环境的变更、与其他软件接口变更的能力?

7.是否包含对“成功”的定义,“失败”的定义?

需求的质量

  1. 需求是用户书写的吗?

  2. 每条需求都不与其他需求冲突吗?

  3. 是否详细定义了相互竞争的特性之间的权衡

  4. 是否避免在需求中规定设计(方案)

  5. 需求是否在详细程度上保持相当一致的水平?有些需求应当更详细的描述吗?有些需求应该更粗略的描述吗?

  6. 需求是否足够清晰,即使转交给一个独立的小组去构建,他们也能理解吗?开发者也这么想吗?

  7. 每个条款都与待解决的问题及解决方案相关吗?能从每个条款上溯到它的问题中的对应跟源吗?

  8. 是否每条需求都是可测试的?是否可应进行独立的测试,以检验满不满足各项需求

  9. 是否描述了所有可能对需求的改动,包括各项改动的可能性

需求的完备性

1.对于在开始开发之前无法获得信息,是否详细描述了信息不完全的区域?

2.需求的完备度是否达到这种程度:如果产品满足所有需求,那么它就是可接受的?

3.你对全部需求都感觉舒服吗?你是否已经去掉了那些不可能完成的需求—那些只是为了安抚客户和老板的东西?

花费在前期准备上的时间长度

花费在问题定义,需求分析,软件架构上的时间依据项目的需要而变化,一般占据10%-20%的工作量和20%-30%的时间

良好的类接口

类的基础是抽象数据类型(我之前大部分时候没有使用抽象,只是把相关的方法和变量定义放在了一起,实际上是不符合面向对象变成原则的),抽象数据类型是指一些数据和
对这些数据所进行操作的集合。定义抽象类有助于代码规范,提高

创建类的原因

(1)为显示世界中的对象建模
(2)为抽象的对象建模
(3)降低复杂度
(4)隔离复杂度
(5)隐藏实现细节
(6)让代码更易重用
(7)把相关的操作包装到一起

应当避免的类

(1)避免创建万能类
(2)消除无关紧要的类
(3)避免用动词命名的类

防御式编程

三种处理进入垃圾的情况

(1)检查所有来源于外部的数据的值
(2)检查所有子程序输入参数的值
(3)决定如何处理错误的输入数据

断言

断言是指在开发期间使用的、让程序在运行时进行自检的代码

异常

用异常通知程序的其他部分发生了不可忽略的错误
只有真正例外的情况下才抛出异常
不能用异常来推卸责任
避免在构造函数和析构函数中抛出异常,除非在同一地方把他们捕获
在恰当的抽象层次抛出异常

变量

与《代码整洁之道》对变量的要求几乎一致

代码改善

软件质量的特性

外在特性:
(1)正确性:指系统规范、设计和实现方面的错误的稀少程度
(2)可用性:指用户学习和使用一个系统的容易程度
(3)效率:指软件是否尽可能少地占用系统资源,包括内存和执行时间
(4)可靠性:指在制定的必须条件下,一个系统完成所需要功能的能力-应该有很长的平均无故障时间
(5)完整性:限制、验证
(6)适应性:适应不同执行环境
(7)精确性:输出结果的精确程度
(8)健壮性:系统在接受无效输入或处于压力环境下持续正常运行的能力
内在特性:
(1)可维护性:指很容易能够对系统进行修改或新增功能,提高性能及修正缺陷
(2)灵活性:系统适用其他系统或者修改难易程度
(3)可移植性:运行环境的可移植性
(4)可重用性:指系统的某些部分可被应用到其他系统的难易程度
(5)可读性:指阅读或理解系统代码的难易程度,尤其是在细节语句的层次上
(6)可测试性:指的是可以进行何种程度的单元测试或者系统测试,以及在何种程度上验证系统是否符合需求
(7)可理解性:指在系统组织和细节语句的层次上理解整个系统的难易程度

协同构建

开发者测试

调试

重构

软件工艺

布局与风格

命令模式

基础概念

基础介绍

(1)概念介绍:
命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。
(2)概念解析:
命令模式将一个请求封装成一个对象,从而可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。

使用场景

在某些场合下,需要对行为进行”记录、撤销/重做、事务”等处理,需要将”行为请求者”与”行为实现者”解耦,将一组行为抽象为对象,可以实现二者之间的松耦合。认为是命令的地方都可以使用命令模式,比如: 1、GUI 中每一个按钮都是一条命令。 2、模拟 CMD。

关键术语

(1)received:真正的命令执行对象
(2)Command:命令
(3)invoker:使用命令对象的入口

实现步骤

以下以后台k8s删除node节点命令为例

a.创建一个命令接口:

接口定义接口名称,需要实现的操作名称,命令模式需要的操作为执行命令,后续可根据不同的命令来实现这个接口和执行命令方法。

1
2
3
4
5
<?php
abstract class Nodes {
public function execute(){}
}
?>

b.创建一个请求类

定义拥有的请求方法
本例中操作有:
(1)列出所有nodes: kubectl get node
(2)删除节点:kubectl delete node $nodeName
(3)查看对应node上的pods信息: kubectl get pods -o wide | grep $nodeName
(4)在删除的node3对应的服务器上执行:kubeadm reset

1
2
3
4
5
6
7
8
9
10
11
12
<?php
class K8s {
public function getNodes()
{
return 'kubectl get node';
}
public function delNodes($nodeName)
{
return "kubectl delete node $nodeName ";
}
}
?>

c.创建实现接口的实体类

针对不同的方法实现命令接口。即实现a步骤(创建一个命令接口)所创建的接口,b中的每个请求方法都需要实现对应的实体类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php
class GetNodes implements Nodes {
private $nodes = new K8s();
public function getK8s():K8s
{
if (empty($this->nodes)) {
$this->nodes = new K8s();
}
return $this->nodes;
}
public function execute()
{
$this->getK8s()->getNodes();
}
}
class DelNodes implements Nodes {
private $nodes = new K8s();
public function getK8s():K8s
{
if (empty($this->nodes)) {
$this->nodes = new K8s();
}
return $this->nodes;
}
public function execute()
{
$this->getK8s()->delNodes();
}
}
?>

创建命令调用类

broker,用来调用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class Broker{
private $commandList = [];
public function addList($k8s)
{
array_push($this->commandList, $k8s);
}
public function executeList()
{
foreach ($this->commandList as $command) {
$command->execute();
}
}
}
?>

使用broker类来接受并执行命令

调用broker类,根据需要(顺序,需要使用的方法等)添加一个执行队列(数组),之后再执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class CommandUse
{
public function executeCommand()
{
$getNodes = new GetNodes();
$delNodes = new DeleNodes();

$broker = new Broker();
$broker->addList($getNodes);
$broker->addList($delNodes);

$broker->executeList();
}
}
?>

执行程序输出结果

命令模式的优点

(1)比较容易设计出一个命令队列
(2)在需要的情况下,可以比较容易的将命令记入日志
(3)允许接收请求的一方决定是否要否决请求
(4)可以容易地实现对请求的撤销和重做
(5)由于加进新的具体命令类不影响其他的类,因此增加新的具体命令类很容易
(6)命令模式把请求一个操作的对象和知道怎么执行一个操作的对象分割开

生成json映射表

生成json映射表代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
$dic = [
'qd' => [...],
'mp' => [... 100],
'jl' => [...],
'sb' => [..., 50, 60, 70, 85, 100],
'ly' => [..., 50, 60, 70, 85, 100],
'dy' => [...0, 50, 60, 70, 85, 100],
'sl' => [...55, 60, 65, 70, 85, 100],
'xl' => [... 50, 55, 65, 75, 85, 100],
'vl' => [...50, 60, 70, 80, 90, 100],
'rh' => [... 100]
];
function getJson($dic) {
$res = [];
foreach($dic as $k => $v){
$res[$k] = [];
foreach($v as $key => $val) {
$res[$k]['mapV'][$key] = [
'label' => (string)$key,
'value' => (string)$val
];
}
}
var_dump(json_encode($res));
}
getJSON($dic);
?>

策略模式

概念

定义

策略模式定义了算法族,分别封装起来,让他们之间可以相互替换,该模式让算法独立于使用它的客户而独立变化

组成

1 抽象策略角色:策略类,通常由一个接口或者抽象类实现
2 具体策略角色:包装了相关的算法和行为。
3 环境角色:持有一个策略类的引用,最终给客户端调用。

分析

策略模式用一个抽象策略角色提供一个类(一般是接口或抽象类),每个策略都实现了这个抽象策略角色,环境角色去调用(依赖注入)

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
<?php
/**
* @author: hexiaojiao@jiapinai.com
* @todo:
* time: 2019-07-30 20:07
*/

/**
* 定义抽象角色类`
* Interface CollectInter
*/
interface CollectInter
{
public function collect($price, $num);
public function input();
}
class Base
{
public function input($discount = 1)
{
fwrite(STDOUT, '请输入单价');
$price = trim(fgets(STDIN));
fwrite(STDOUT, '请输入数量');
$num = trim(fgets(STDIN));
$res = $this->collect($price, $num, $discount);
return $res;
}
}

/**
* 定义具体策略类
* Class Collect02
*/
class Collect02 extends Base implements CollectInter
{
public function collect($price, $num, $discount = 1) {
var_dump('Collect02:', $price * $num * $discount);
return true;
}
}

/**
* Class Discount
*/
class Discount extends Base implements CollectInter
{
public function collect($price, $num, $discount = 0.8)
{
var_dump('Discount:', $price * $num * $discount);
return true;
}
}

/**
* Class Reduce
*/
class Reduce extends Base implements CollectInter
{
public function collect($price, $num, $total = 100, $reduce = 0)
{
if ($price * $total >= $total) {
var_dump('Reduce:', ($price * $total) - $reduce);
} else {
var_dump('Reduce:', $price * $total);
}
return true;
}
}
/**
* 环境角色类
* Class Main
*/
class Main
{
private $_strategy;
private $_isChange;
public function __construct(CollectInter $collectInter)
{
$this->_strategy = $collectInter;
}
public function change(CollectInter $collectInter)
{
$this->_strategy = $collectInter;
$this->_isChange = true;
}
public function beginCollect()
{
if ($this->_isChange) {
echo "改变收银方式:";
$this->_strategy->input();
} else {
$this->_strategy->input();
}
}
}
$strategy = new Main(new Discount());
$strategy->beginCollect();
$strategy->change(new Collect02());
$strategy->beginCollect();

运行结果

1
2
3
4
5
6
7
8
请输入单价4
请输入数量2
string(9) "Discount:"
int(8)
改变收银方式:请输入单价7
请输入数量3
string(10) "Collect02:"
int(21)

Leetcode刷题

1 两数之和

描述
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
示例

1
2
3
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

`

fast-json-patch

fast-json-patch包使用

网上关于json-patch的中文介绍太少了,官方文档没有翻译版,所以写一下自己的学习文档,以下只是自己根据英文文档的理解,不保证正确。

基础操作

json patch几大基础操作包括:add, replace, move, test, remove, copy

applyPatch

给定文档和操作,执行操作

1
2
3
4
5
6
7
8
import {applyOperation, applyPatch} from 'fast-json-patch';
let document = document = { firstName: "Albert", contactDetails: { phoneNumbers: [] } };
let patch = [
{ op: "replace", path: "/firstName", value: "Joachim" },
{ op: "add", path: "/lastName", value: "Wester" },
{ op: "add", path: "/contactDetails/phoneNumbers/0", value: { number: "555-123" } }
];
const docu = applyPatch(document, patch)

applyOperation

执行单独的操作而不是连贯操作,举例如下:

1
2
3
4
var document = { firstName: "Albert", contactDetails: { phoneNumbers: [] } };
var operation = { op: "replace", path: "/firstName", value: "Joachim" };
document = jsonpatch.applyOperation(document, operation).newDocument;
// document == { firstName: "Joachim", contactDetails: { phoneNumbers: [] }}

applyReducer

看了英文原文文档并不很确定,只能根据自己的理解来看。
javascript的reduce是一个累加器,使用applyReducer作为累加操作,当定义的操作是一组数组时,使用applyReducer执行操作,示例如下:

1
2
3
4
5
6
7
8
var document = { firstName: "Albert", contactDetails: { phoneNumbers: [ ] } };
var patch = [
{ op:"replace", path: "/firstName", value: "Joachim" },
{ op:"add", path: "/lastName", value: "Wester" },
{ op:"add", path: "/contactDetails/phoneNumbers/0", value: { number: "555-123" } }
];
var updatedDocument = patch.reduce(applyReducer, document);
// updatedDocument == { firstName:"Joachim", lastName:"Wester", contactDetails:{ phoneNumbers[ {number:"555-123"} ] } };

Generating patches

提供一个类似于观察者,先获取原始文档结构(observe),之后文档进行变化,最后执行操作,可以得到应有的操作流程。示例如下:

1
2
3
4
5
6
7
8
9
10
11
var document = { firstName: "Joachim", lastName: "Wester", contactDetails: { phoneNumbers: [ { number:"555-123" }] } };
var observer = jsonpatch.observe(document);
document.firstName = "Albert";
document.contactDetails.phoneNumbers[0].number = "123";
document.contactDetails.phoneNumbers.push({ number:"456" });
var patch = jsonpatch.generate(observer);
// patch == [
// { op: "replace", path: "/firstName", value: "Albert"},
// { op: "replace", path: "/contactDetails/phoneNumbers/0/number", value: "123" },
// { op: "add", path: "/contactDetails/phoneNumbers/1", value: {number:"456"}}
// ];

比较两个文档-compare

直接比较两个文档,得出从文档A到文档B应有哪些操作(得到操作后可以尝试用这个操作来更新B文档)

1
2
3
4
var documentA = {user: {firstName: "Albert", lastName: "Einstein"}};
var documentB = {user: {firstName: "Albert", lastName: "Collins"}};
var diff = jsonpatch.compare(documentA, documentB);
//diff == [{op: "replace", path: "/user/lastName", value: "Collins"}]

验证修补程序序列

对patch做校验,验证这个补丁是否可以应用到指定的对象上(可能是这个意思),如果出错可以打印出错误信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var obj = {user: {firstName: "Albert"}};
var patches = [{op: "replace", path: "/user/firstName", value: "Albert"}, {op: "replace", path: "/user/lastName", value: "Einstein"}];
var errors = jsonpatch.validate(patches, obj);
if (errors.length == 0) {
//there are no errors!
}
else {
for (var i=0; i < errors.length; i++) {
if (!errors[i]) {
console.log("Valid patch at index", i, patches[i]);
}
else {
console.error("Invalid patch at index", i, errors[i], patches[i]);
}
}
}

Json Patch API

函数原型:

1
function applyPatch<T>(document: T, patch: Operation[], validateOperation?: boolean | Validator<T>, mutateDocument: boolean = true, banPrototypeModifications: boolean = true): PatchResult<T>

对参数的释义:

  • document:将要patch的文档;
  • patch:一个json-patch数组,一组可执行的操作;
  • validateOperation: boolean类型,是否使用默认校验器对每个操作进行校验或通过校验器回调;

    其余部分可以自行查文档

简单工厂模式

引入

实现计算器

代码实现

1 实现一个基础的计算器功能,代码见https://github.com/laurel-he/design_pattern/blob/master/simpleFactory/calculator01.php

问题分析

(1)错误处理只判断了除数是否为0,对于字符超长,不可计算等都未处理,可以加上try catch;
(2)代码不可复用,耦合性很高

使用面向对象处理

(1)使用面向对象的方式实现,将输入输出流和逻辑代码分离,可以提高代码复用性,降低耦合,代码见https://github.com/laurel-he/design_pattern/blob/master/simpleFactory/Calculate2.php

紧耦合vs松耦合

思考:什么情况下使用继承和多态(各种运算可以继承自运算基类,便于扩展,多态考虑输入的不同类型,对于字符串怎样运算)
根据以上思考,完成有继承和多态的代码如下:
https://github.com/laurel-he/design_pattern/blob/master/simpleFactory/Calculate03.php
思考:以上代码实现方式虽然使用到了继承,但是如何知道应该调用哪个类呢?难道像之前预估的一样,还是要使用switch判断?

简单工厂模式

解决问题,实例化谁,将来会不会增加实例化的对象等容易变化的地方,考虑用一个单独的类来做这个创造实例的过程
在此基础上实现一个简单工厂类,代码如下:
https://github.com/laurel-he/design_pattern/blob/master/simpleFactory/Calculate04.php
如果需要修改运算,可以只修改对应的类,如果需要添加运算,只需要添加运算类,并在工厂中添加对应的分支就可以了
简单工厂模式的工厂类一般是使用静态方法,通过接受的参数的不同来返回不同的对象实例

工厂方法模式

1 简单工厂模式优点:
(1)简单工厂包含必要的判断逻辑,实现了对象的创建和使用的分离;
(2)客户端无需知道所创建的具体产品类的类名,只需要具体产品类对应的参数即可;
(3)在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性
2 简单工厂模式缺点:
(1)工厂类职责过重,它出问题整个系统都会崩溃
(2)添加新的类的时候,系统中的简单工厂类都要修改,违反了开放-封闭原则
(3)简单工厂的静态方法,使得工厂角色无法形成基于继承的等级结构
工厂方法模式每一种算法都对应一种工厂,
工厂方法模式优点:
(1)

抽象工厂