An Introduction
We trust you have received the usual lecture from the local System Administrator. It usually boils down to these three things:
- 1) Respect the privacy of others.
- 2) Think before you type.
- 3) With great power comes great responsibility.
Agile
CI
Continuous Integration
持续集成已经是敏捷编程的基石之一. 在 TW 网站 上列出了比较流行的 CI Server. 在现在流行的 Java 领域中, 比较流行和有名的 CI Server 有:
Contents
CruiseControl
CruiseControl by Thought Works,
Hudson
Maybe the mostly used CI Server now.
LuntBuild
QuickBuild
Business version of LuntBuild. Now it has a community Edition, full functional, but limited on configuration-number.
TeamCity
TeamCity by Jetbrains. Most used among propriety CI Servers. It also has a free version.
AntHillPro
Bamboo
Bamboo by Atlassian, who developed Jira and Wiki Confluence.
Functions
The basic functionality of CI Server is a 定时调度器. Set a timer, and do some job. This function can be achieved by home-made tools, like cron + Ant/Maven.
The extra functions of CI Server used in corporation: - 统一管理: 100+ 项目 - 流程管理: Daily Build -> QA Build -> Release Build - 权限管理: connect to Active Directory, managing user authorization - 持续测试: Continuous Testing, 产生详尽的报告, 收集测试统计数据以分析和考量项目 - 持续质检: Continuous Code Quality Analysis, 处理项目产生的 Coverage 报告, 代码静态分析报告 - SCM集成: 与 VCS 集成, 如 ClearCase, Subversion, StarTeam - 工具集成: bug tracking, IDE, etc
config
一般使用 Project, Project Group 管理. TeamCity 的导航最方便. LuntBuild 和 QuickBuild 使用 Configuration/配置 的概念, 一个配置处理一个最基本的流程, 如 CheckOut, Build, Publish Artifacts; 这两者的人性化方面有所不足.
QuickBuild 唯一支持配置树, 使用 Configuration 而不是 Project, 对多项目最为实用. QB 使用继承关系, 完成基本配置后, 其它的修改一下参数即可.
TeamCity 支持 Project Group 和 Project 两层关系, 也没有继承关系, 只有 copy project 功能. 而 QB 可以拷贝整个子树, 因而可以制作配置模板, 在不同部门和项目间使用变量来控制.
TeamCity 配置起来最方便. 另外它有 JUnit, NUnit 类的 Tests, 不用写 Ant 脚本, 可以直接找出项目里的 unit tests, 这个是它独有的.
CruiseControl, Hudson, Bamboo 没有特别的优点.
Daily Build, QA Build, Integration Build, Release Build. 每天, 每个 Sprint 做 QA 和 Integration.
TBD
如果团队使用 XP 编程, 要做 Commit Test Build, 每个提交自动触发一个 build, 保证构建和unit-tests和code-quality不受破坏. 即 Continuous testing 和 continuous code quality analysis, 这要利用 JUnit, NUnit, CheckStyle, PMD, Cobertura, FxCop 等工具实现.
Scrum
Agile & Lean
Lean –> Lean Software –> Scrum <– Design Pattern v v –> Kanban <– Agile <– eXtreme Programming –> Lean Startup
Agile: 质量, 生产率, 价值 Lean: 消除浪费
Agile: Scrum, 框架和过程; XP, 实践; FDD Kanban, Lean DSDM, crystal
[Video]: Jeff Sutherland breaks down the structure of scrum [Docu]: Scrum Guide by Ken Schwaher
Meeting: Beginning, End, Daily Role: Scrum Master, Product Owner, Team Reporting: Burn-down chart, self-reporting
Daily Meeting:
- what everyone did
- what everyone going to do
- how one can help each other
问题-措施-改进 的闭环改进
v Product Backlog, 梳理 v 工作计划, 规划 v 冲刺列表 v 每日列表 v 冲刺执行 v 产品增量, 潜在可交付 v 冲刺评审 v inspect adapt v 冲刺回顾
Scrum Guide
Annually Release
Scrum 定义了角色, 事件, 工件, 和它们的组织规则.
- 透明: 对产出负责的人, 要有统一理解, 对完成有一致的理解
- 检视: 经常检视工作和目标的完成进度
- 调整: 对流程超出标准的偏差, 对流程和流程的内容调整并尽快实施
透明性是一个学习, 说服, 改变的过程, 不会一步完成
3-6 个月来有效使用 scrum, 按其机制运转
迭代增量: 优化可预测性和风险管理
Scrum Master 服务于 PO, 团队, 组织
- meetings
- sprint 计划会
- 每日 scrum 站会
- sprint 评审会
- sprint 回顾会
- Product Owner: 产品负责人
- backlog 唯一负责人
- 清晰表达产品待办列表项
- 列表排序, 实现目标和使命
- 优化团队所执行工作的价值
- 清晰, 可见, 透明, 对每一个人展示下一步工作
- 使团队对待办项有足够的理解
- 团队
- 自组织, 跨职能
- 最大化, 灵活性, 生产效率
- 没有子团队, 每人有专长
- 但责任属于团队
团队规模 3-9 人, 人少时没有足够互动, sprint 中技能受限; 人多需要过多协调沟通, 复杂性太高, 不便于经验管理. 团队要拥有开发产品增量的全部技能.
事件. 每个 sprint 作为一个项目, 周期在一个月内, 一般两周. 长度和时间节点必须固定, 完成的内容可调整.
- 计划会: 1. 下个周期要做什么, 要包含什么内容; 2. 怎么完成所需的增量工作
- 站会: 只有开发人员可参与
工作
- 产品待办列表: 有序列表, a list item 包含: 描述, 次序, 估算, 价值
- 列表包含: 特性, 功能, 需求, 改进, 修复 等等对 sprint 有价值的任务和资源
监控实际目标的进度
- burn-downs
- burn-ups
- cumulative flows 累积工作流
Software Development
软件开发中要考虑的工程因素有进度, 成本, 质量, 维护 等等. 而现在软件开发中要面对需求变更, 不定需求, 无用户需求. 为解决这些问题, 软件行业在以下几个方面做出了成功的尝试: 流程工具, 设计, 测试, 人力.
对于团队, 要做人员备份和 周期轮换.
历史
产品实现的开发管理中需求, 设计, 实现, 验证, 维护所需要投入比成为了 7:6:7:13:67, 使软件开发难以为继.
- NATO 在 1968 年提出的软件工程的概念, 强调软件开发的 系统化, 规范化, 数量化.
- Royce 在 1960 年提出了瀑布模型, 按照工程的方式为软件开发划分了阶段.
阶段划分使软件开发进度有所依循, 有 deadline/milestone 来跟踪阶段.
但瀑布模型的阶段间缺少反馈, 开发的结果在后期才可以清楚得看到, 问题的追溯与修正代价太大, 使得它不适应需求的变化.
现在的做法是在每个迭代中应用瀑布模型.
开发中目前的问题有需求和市场两方面. 需求通常是不确定的, 会不断变动的, 在开发初期无法掌握细节, 容易造成过度设计, 过度开发. 市场中则充满(同质)竞争, 市场的快速变化带来需求的快速变化, 需要开发过程能够快速响应.
需求的不明确和频繁变化 -> 功能变更 -> 交付时间短 -> 测试不充分 -> 交付质量差 -> 修改成本 需求的不明确和频繁变化 -> 功能变更 -> 设计修改 -> 设计质量低 -> 修改成本 | 变更
现在的开发是价值导向的. 团队的稳定性, 才可有效保持开发效率的可预测性. 资源是确定的, 时间是确定的, 根据资源和时间来调整需要产出的功能.
- 敏捷开发 在 2001.02 提出
目标, 质量, 生产率
工具方法: Scrum, XP, FDD, DSDM, ASD
价值观原则: 让软件开发团队快速工作, 响应变化
开发思想: 敏捷 agile
方法族: 以人为核心, 迭代, 循序渐进
敏捷开发中, 下列左侧比右侧有更大价值. 个体和交互 > 过程和工具 可以工作的软件 > 面面俱到的文档 客户合作 > 合同谈判 响应变化 > 遵循计划
individuals and interactions working software customer collaboration responding to change
12 Rules of Agile 1. 尽早, 持续, 交付 2. 欢迎改变需求 3. 可工作的软件, 经常交付 4. 业务人员和开发人员一起工作 5. 个体构建项目 6. 团队内与团队间面对面交流 7. working software 8. 可持续 9. 优秀技能, 好的设计 10. 简单 11. 自组织团队 12. 定时反省
Agile 是要做有价值的事, 是理念, 优秀实践, 具体应用. 基本特征: 迭代开发, 增量交付, 客户互动, 持续集成, 自我管理. 方法: Scrum, XP, FDD, 看板(限制在制品)
迭代一般是十四天 fortnight. 开发时有计划, 结束时有回顾.
- 精益 1950s from Toyota 生产体系 1950s 1990 1996 2000 2003 2006 2011
基本特征: 价值流, 尊重一线人员, 消除浪费(库存, 次品, 传送), 拉动式生产, 质量内嵌(生产环节早发现问题), 整体优化.
价值, 持续的短交付周期, 尊重他人, 持续改善. - 不给”客户”带来麻烦 - 先发展员工, 再构建产品 - 无浪费性工作
14 原则
研发管理体系现状: IDD, CMMI, 敏捷, 精益.
以人为本, 持续改善. There is no silver bullets.
- Most Parts of the procedure are related, and can onlhy be achieved together, but not separately.
- The git commit/push can have a reviewer assigned, to check the code and style.
- Embrace the change, and keep evolving
- DevOps
Unit Test
Questions
- C 语言的单元测试工具
About Unit Test
WHAT
对一个明确的功能/函数, 测试其在给定条件下的工作是否正确.
Unit test > Integration test > Automated Gui-tesst > Manual Test
Unit, Components: 成本低, 效率高, 缺陷晚定位 Acceptance, API User Interface, Manual: 反映真实需求, 接近业务
WHY
- 验证每一项功能的正确性, 为后续开发提供支撑
- 回归性. 避免代码修改影响原有功能
- 思考设计. Test-First, 要把程序设计成易调用, 可测试, 低耦合的结构
- 本身是可工作的文档. A working/functional document, 展示函数/类/接口的功能的使用方法
Value
- 要了解功能和函数行为, 才能写好单元测试
- 写单元测试是用现在的时间换将来的时间
- 单元测试是白盒测试
HOW
- 测试工具
- 断言 assert
- Mock @Runwith @Mock @Test
Java: JUnit, Hamcrest, Mockito
Test
单元测试要点: Fast, Isolated, Repeatable, Self-verifying, Timely
Given, When, Then 模式
One test, One function, 一个测试测一个方法
Mock
适用于所 mock 的对象
- 难以创建
- 难以触发
- 响应缓慢
- 用户界面
- 尚未存在
TDD, Test Driven Developing
一种增量式软件开发方法
实践: If tests pass, write no codes.
XP, eXtreme Programming
Algorithm
Huffman Encoding
class Node(object):
def __init__(self, symbol = '', weight = 0, left = None, right = None):
self.symbol, self.weight, self.left, self.right = symbol, weight, left, right
if (left and right):
self.weight = left.weight + right.weight
def result(self, code = ''):
r = []
self.symbol and r.append((self.symbol, self.weight, code))
self.left and r.extend(self.left.result(code + '0'))
self.right and r.extend(self.right.result(code + '1'))
return r
def genHuffman(char, freq):
nodeList = []
for i in range(len(char)):
nodeList.append(Node(char[i], freq[i]))
while (len(nodeList) >= 2):
nodeList.sort(key = lambda x: x.weight, reverse = True)
nodeList.append(Node(left = nodeList.pop(), right = nodeList.pop()))
return nodeList[0] if nodeList else Node()
def getFrequency(freq):
total = sum(freq)
return list(map(lambda x:x/total, freq))
# test
symbol = ['A', 'B', 'C', 'D', 'E']
counts = [15, 7, 6, 6, 5]
print(genHuffman(symbol, counts).result())
# output
# space 7 111
# a 4 010
# e 4 000
# f 3 1101
# h 2 1010
# i 2 1000
# m 2 0111
# n 2 0010
# s 2 1011
# t 2 0110
# l 1 11001
# o 1 00110
# p 1 10011
# r 1 11000
# u 1 00111
# x 1 10010
symbol = ['space','a','e','f','h','i','m','n','s','t','l','o','p','r','u','x']
counts = [7, 4, 4, 3, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1]
for e in sorted(genHuffman(symbol, counts).result(), key = lambda x:x[1], reverse = True):
print (e[0], e[1], e[2])
Huffman encoding goes from leaves to root, unlike Shanno-Fano encoding which goes from root to leaves.
A simple alg using a priority queue.
- Create a leaf-node for each symbol, with the info of the symbol’s frequency.
- Loop while there are more than 1 node in the queue
- Replace the 2 nodes of lowest probability, with 1 node that takes the 2 nodes as children and its weight is the sum of the 2 nodes’ weight.
- The last one node is the root of the completed tree.
C
Memory
#include <string.h>
int g_a = 0; // global, initialized
int* g_p; // global, uninitialized
int main() {
int a; // stack
char s[] = "ab"; // s: stack, "ab" constant
char *p1; // static
char *p2 = "12"; // p2: stack, "12" constant
static int b = 0;// static
g_p = (char *)malloc(8); // heap
p1 = (char *)malloc(8); // heap
strcpy(g_p, "12"); // "12" constant
// the memory might be optimized by compiler and use the same memory as p2
return 0;
}
内存分配方式中的 栈、堆-自由存储区、全局与静态存储区、常量存储区
栈
栈是由编译器管理的变量存储区,在需要时分配,在不需要时自动释放。用户栈位于用户进程地址空间的顶部,在执行期间可以动态地扩展和收缩。栈中的变量有局部变量、函数参数,编译器也会用栈来实现函数调用。堆
堆是由用户程序主动管理的内存块,编译器不做管理。堆内存用 malloc/calloc/realloc 分配,用 free 释放;或用 new 分配,用 delete 释放。没有释放的内存在用户程序结束后由操作系统回收。堆可以*动态地扩展和收缩*。
在 Linux 上,malloc 和 new 分配的内存都在堆上,但两种分配所执行的操作有所不同。全局与静态存储区
全局变量和静态变量的内存存储区。在 C++ 中为一块内存区。在 C 中,静态变量和初始化的全局变量在一块内存区,未初始化的全局变量在相邻的内存区。可通过 void* 来操作未初始化变量的存储区。全局和静态变量都在程序结束时由系统进行释放。常量存储区
常量存储,不允许修改其中的变量。
堆栈的区别
堆和栈的区别是大家常说起的一个问题。
#include <stdlib.h>
int main() {
int* a = (int*) malloc(4);
free(a);
return 0;
}
指针 a 在栈上分配了一段内存,malloc 在堆上分配了一段4个整型大小的内存,然后把这段内存的首地址返回给指针 a。
堆和栈的区别主要有5点。(中文中“堆栈”指栈。)
管理方式
栈由编译器自动管理;堆由用户程序主动管理,显示进行分配和释放。空间大小
栈一般有一定的空间大小,而且默认的空间大小较小,例如1M;而堆在32位系统下的地址空间就可以达到4G。碎片
栈是 FILO 队列,永远不会从中间 pop 内存块。而堆在大量的 malloc/free, new/delete 内存空间会产生许多的不连续,生成内存碎片。分配方向
堆地址的分配是从小往大,向着内存地址增加的方向进行;栈地址的分配是从大往小,向内存地址减小的方向进行。分配方式 这部分解释有问题
栈的静态分配由编译器完成。堆是动态分配的,没有静态分配。 栈的动态分配,使用 malloc,但由编译器进行释放。分配效率
栈比堆的效率更高。栈在系统层有更多的支持,有专门的指令进行压栈和出栈,有专门的寄存器存放栈地址。堆由函数库提供支持。 在堆上分配一块内存时,库函数会按照特定算法在堆中查找足够大小的可用空间,如果由碎片过多等原因没有足够大小的空间,则调用系统功能去增加程序数据段的空间,以产生足够大小的内存。
和栈相比,堆没有专门的系统层支持,可能引发用户态和核心态的切换(什么意思)使内存申请的代价更大,大量的分配和释放操作后容易产生大量内存碎片。在效率更重要的地方,比如函数调用,是利用栈来完成的。函数调用中的参数,返回地址,EBP,局部变量都由栈来存放。而堆比栈更灵活(怎么灵活的),在分配大量内存空间时,优先使用堆。
堆和栈都是对存空间的操作,要防止内存越界问题。内存越界会使程序产生意外的结果,或使程序崩溃。
静态变量
原因:控制函数中生存周期不依赖于函数自身周期的变量的可见性
函数内部的变量在其定义被执行时在栈上分配空间,在函数结束时会释放它在栈上分配的空间。如果要在一个函数的多次调用间传递数据,可以使用全局变量;如果要限制该变量的作用范围,则使用静态变量。控制作用范围,可以保持类的封装性,使成员对外不可见。
实现机制
静态数据成员要在程序开始运行时就存在。因为要独立于函数在栈上的空间,静态数据成员不能在函数内分配空间和进行初始化。有三个地方可以为静态变量分配空间:1 main 函数之间的全局数据定义/声明处;2 类外部接口的头文件中的类声明处(可以吗?);3 类定义的内部实现(这是为什么可以?) 这是有类的成员函数定义。
静态变量存储在程序的静态存储区中,而不是在栈上。静态变量按出现的先后顺序初始化,如果有嵌套,则要保证所嵌套的成员已初始化。释放时则按相反顺序进行。
优势 静态变量是其所有对象共有共用的,只存储在一处,所有对象存取同一值,不需额外进行传递。它可以缩短子类对父类静态成员的访问时间,节省子类的空间。
public
的静态变量可以用 ClassName::staticMemberName
引用。
- 注意点
- 静态数据成员必须进行初始化。
DataType ClassName::staticMemberName = value
初始化在类外进行,用作用域运算符来标明所属类,不使用 static,private,public。 (使用 static 为定义新静态变量。) - 类的静态成员函数属于整个类而非类的对象,所以它没有 this 指针,只能访问类的静态数据和静态成员函数。
- 静态成员函数不能定义为虚函数。
- 静态成员变量的地址(需要更多资料)是指向其数据类型的指针,静态成员函数的地址类型是 nonmember 函数指针。
- 静态成员函数是一个 callback 函数(为什么?),可以结合 C++ 和 C-based X window 系统,也可以应用于线程函数上(怎么用?)。
- 因为静态变量可以继承,子类可以定义与父类相同的静态变量来进行屏蔽。
- 静态数据成员必须进行初始化。
Design
Design Pattern
设计模式是经过验证的代码设计经验的总结。其关注点是代码的可重用、易理解、可靠性。
设计模式的基本类型有:创建、结构、行为。
设计原则
设计模式是设计原则的实现,以达到代码的复用和可维护。
单一职责原则 Single Responsibility Principle
开闭原则 Open Closed Principle
Software entities should be open for extension, but closed for modification. 模块应对扩展开放,对修改关闭。里氏代换原则 Liskov Substitution Principle
子类可代替父类运行。这种代换是继承利用的基础。依赖倒转原则 Dependency Inversion Principle
子类代替父类后,程序的行为不变化。这是面向对象设计的标志。接口隔离原则 Interface Segregation Principle
各接口间互不影响。每个接口可以做而且仅做自己需要做和应该做的事。不是所有可以做的事都应放到一个接口中。合成聚合复用原则 Composite/Aggregate Reuse Principle
应优先使用合成与聚合,而不是继承。合成复用原则是在新对象中使用已有对象,新对象通过向已有对象委派来复用已有功能。使各个类之间的联系尽量少,才好提高扩展性和可维护性。迪米特最小知识原则 Principle of Least Knowledge
各个对象对其他对象的了解应尽可能少。
在解决问题时,设计模式可以帮忙我们在更高的抽象层次上工作。先是对问题的分析,了解问题中的因果关系,看使用模式的先决条件是否满足。然后根据分析给出解决方案。解决方案不是具体的设计和实现,而是对问题做抽象描述,表明如何用元素的组合而解决问题。
创建
- 单例模式 Singleton 下一个类仅有一个实例和一个全局访问点。要注意单例模式在使用多线程、序列化、类装载器(multi-threading, serialization, class loaders)时的问题。 适用条件: 类只能有一个实例,可以从全局访问;实例可通过子类扩展。
- 工厂模式 Factory Method 定义一个用于创建对象的接口,由子类确定实例化哪个对象,使类的实例化延后到子类。 适用条件: 一个类希望/需要由其子类来创建对象。
- 抽象工厂模式 Abstract Factory 不指定具体的类,仅提供一个创建一组相关/相依赖对象的接口。 适用条件: 只提供接口而不是实现。联合使用一系列的对象。
- 建造者模式 Builder 将(复杂)对象的构建和表示分离,使同样的构建过程可以创建不同的表示。 适用条件: 对象创建的算法应独立于对象,或对不同的对象有不同的表示。
- 原型模式 Prototype 用原型的实例来指定所创建对象的种类,并通过复制这个原型来创建新的对象。 适用条件: 在运行时才指定实例化的类,如动态装载;类的实例只会是几个不同状态组合中的一种时。
结构
- 适配器模式 Adapter 将一个类的接口转换成另一个接口,使接口不兼容的类可以一起工作。 适用条件: 使用已存在的类,但接口不合要求;新建可复用的类,与不相关或不可预见的类兼容。
- 桥接模式 Bridge 将类的抽象部分和实现部分分离,分别独立改变。 适用条件:
- 装饰模式 Decorator 向对象动态增加额外职责。比生成子类更灵活地扩展一个类的功能。 适用条件:
- 组合模式 Composite 将对象组合成表达整体与部分关系的树形层次结构,使单个对象和复合对象在使用上一致。 适用条件:
- 外观模式 Facade 为子系统的一组接口提供一致的界面,定义高层接口,增加子系统的易用性。 适用条件:
- 享元模式 Flyweight 用共享技术有效支持大量细粒度的对象。 适用条件:
- 代理模式 Proxy 为其他对象提供代理,用代理来控制对象的访问。 适用条件:
行为
- 模版模式 Template Method 定义方法中的算法骨架,将部分步骤延后到子类中,使子类不改变算法结构而重定义算法中的某些步骤。 适用条件:
- 命令模式 Command 将请求封装为对象,??使用不同请求来对接收者进行参数化??,可将请求排队、取消、记录日志。 适用条件:
- 迭代器模式 适用条件:
- 观察者模式 Observer 在对象间建立一对多的依赖关系,当一个对象改变时,所有依赖于它的对象都自动刷新。 适用条件:
- 中介者模式 Mediator 封装一系列的对象交互,使各对象不需要显示相互引用,松耦合,并可以独立改变对象间的交互。 适用条件:
- 备忘录模式 Memento 不破坏对象的封装,捕获对象内部的状态并在对象外部保存,这可将对象恢复到保存时的状态。 适用条件:
- 解释器模式 Interpreter 定义一个给定语言的文法表示和解释器,来表示解释此语言的语句。 适用条件:
- 状态模式 State 允许对象在内部状态改变时改变行为,像是对象所属的类发生了变化。 适用条件:
- 策略模式 Strategy 定义并封装一系列的算法,并使它们可相互替换,使算法变化独立于使用者。 适用条件:
- 责任链模式 Chain of Responsibility 解耦请求的发送者和接收者。将多个可能处理请求的对象连成一条链,请 适用条件: 求在链上传递,直到有一个对象处理它。
- 访问者模式 Visitor 表示对一个对象中元素的操作,可以定义作用于元素的新操作而不改变元素的类。 适用条件:
Reactive System
响应式系统,响应式编程
事件驱动实现响应式编程,消息驱动实现响应式系统。
响应式编程强调的是数据流而非控制流。
例子:
- Futures / Promises:值的容器,many-read and single-write
- 响应式 流:无限制的数据处理流,支持异步,非阻塞,多个源与目的
- 反压转换管道 Back-pressure transformation pipelines
- 数据流变量:依赖于输入,过程procedures 或其他单元的 单赋值变量(存储单元)single assignment variables,能够自动更新值的改变。如:表格软件中一个单元格值的改变会影响到所有依赖于它的函数,顺流而下地使它们产生新的值。
JVM 中支持 非阻塞式反压异步流,响应式编程 的流行库有:Akka Streams, Ratpack, Reactor, ExJava, Vert.x
响应式编程的基本好处是:提高多核和多 CPU 硬件的计算资源利用率;通过减少序列化点来提高性能(阿姆达尔定律,古瑟通用可伸缩定律 Amdahl, Guenther)。
响应式编程中,active 活动组件间一般不需要明确的协作,而避开了传统的编程范式的做法:尽力提供一个简单直接的可持续的方法来处理异步非阻塞计算和 I/O。
响应式编程的价值在于组件的创建和工作流的组合。在异步执行上加入反压以避免过度使用/无限度地消耗资源。
为在更高层次上理清一个系统,设计响应式系统,需要为其设计响应式架构。响应式编程仅是一种编程范式,要注意它的适用条件和情形。
事件驱动与消息驱动
响应式编程的着眼点在短时数据流链条上的计算,因而使用事件驱动;响应式系统关注于通过分布式系统的通信和协作所得到的弹性和韧性,使用消息驱动 messaging。
事件驱动的数据流驱动模型,拥有 long-live addressable 长期存活可寻址 组件的消息驱动系统,两者的不同在于,消息具有固定的导向,事件没有;消息会有一个明确的去向,而事件只是一个等待被观察的信息。消息式结构更适用于异步,因为消息的发送与接收和发送者与接收者是分离的。
一条消息就是一则被送往一个明确目的地的数据。一个事件则是达到某个给定状态的组件发出的一个信号。在一个消息驱动的系统中,可寻址到的接收者等待消息的到来然后响应它,否则保持休眠状态。在一个事件驱动系统中,通知的监听者被绑定到消息源上,这样当消息被发出时它就会被调用。这意味着一个事件驱动系统专注于可寻址的事件源,而消息驱动系统专注于可寻址的接收者。
分布式系统需要通过消息在网络上传输进行交流,以实现其沟通基础,而事件的发出则是本地的。常见的做法是在底层通过发送包裹着事件的消息来搭建跨网络的事件驱动系统,这样能维持在分布式环境下事件驱动编程模型的相对简易,可以用在合理范围内的特殊案例上。
分布式环境下的事件驱动在编程模型的抽象性和简易性上有好处,但在控制性上有欠缺。消息强迫我们去拥抱分布式系统的真实性和一致性。你需要去考虑局部错误,错误侦测,丢弃/复制/重排序消息 (partial failures, failure detection, dropped/duplicated/reordered),还有一致性问题,管理多个并发真实性(并发真实性?)。你需要面对它们,处理它们,而不是藏在低劣的抽象层后,假装网络并不存在,像是 EJB, RPC, CORBA, XA。
在设计中,这种语义和适用性上的不同对应用有深刻的影响,包括分布式系统的复杂性中的弹性,韧性,移动性,位置透明和管理。
在使用了响应式编程技术的响应式系统中,有用于沟通的消息,也不呈现现实的事件。
响应式系统
响应式程序与系统
弹性
韧性
生产效率
编程与系统关联
总结
流,轻量,实时
分布式环境下,实现技术,工具,设计模式
SCALE, 负载平衡,主动执行
FRP(FP) is misused
RP,由有效信息推动,而非执行流程/线程推动的控制流。信息实现异步非阻塞与 IO 非绑定。
Dist
Kafka
Elastic Compute
Editor
Emacs
Emacs Config File
VIM Shortcuts
退出
- w filename: 保存正在编辑的文件filename
- wq filename: 保存后退出正在编辑的文件filename
- q:退出不保存。
窗口操作
- ctrl+w p: 在两个分割窗口之间来回切换。
- ctrl+w j: 跳到下面的分割窗
- ctrl+w h: 跳到左边的分割窗。
- ctrl+w k: 跳到上面的分割窗。
- ctrl+w l: 跳到右边的分割窗。
移动
h,j,k,l: 左,下,上,右。
w: 下一个词的词首。
e:下一个词的词尾。
b:上一个词的词首。
<>: v 模式选中后进行缩进。
跳转
%: 可以匹配{},"",(),[]之间跳转。
H、M、L:直接跳转到当前屏幕的顶部、中部、底部。
#H:跳转到当前屏的第#行。
#L:跳转到当前屏的倒数第#行。
zt: 当前编辑行置为屏顶。
zz: 当前编辑行置为屏中。
zb: 当前编辑行置为屏底。
G:直接跳转到文件的底部。
gg: 跳转到文件首。
():跳转到当前的行首、行尾。
{}:向上、向下跳转到最近的空行。
[{:跳转到目前区块开头。
]}:跳转到目前区块结尾。
0: 跳转到行首。
$: 跳转到行尾。
2$: 跳转到下一行的行尾。
#:跳转到该行的第#个位置。
#G: 15G,跳转到15行。
:#:跳转到#行。
f'n':跳转到下一个"n"字母后。
ctrl+b: 向后翻一页。
ctrl+f:向前翻一页。
ctrl+u: 向后翻半页。
ctrl+d: 向前翻半页。
ctry+e: 下滚一行。
选择
1.V: 选择一行。
2.^V: 矩形选择。
3.v3w: 选择三个字符。
编辑
1. 新增:
i: 光标前插入。
I: 在当前行首插入。
a: 光标后插入。
A: 当前行尾插入。
O: 在当前行之前插入新行。
o: 在当前行之后插入新行。
2. 修改 c(change) 为主:
r: 替换光标所在处的字符。
R:替换光标所到之处的字符。
cw: 更改光标所在处的字到字尾处。
c#w: c3w 修改3个字符。
C:修改到行尾。
ci':修改配对标点符号中的文本内容。
di':删除配对标点符号中的文本内容。
yi':复制配对标点符号中的文本内容。
vi':选中配对标点符号中的文本内容。
s:替换当前一个光标所处字符。
#S:删除 # 行,并以新文本代替。
3. 删除 d(delete) 为主:
D:删除到行尾。
X: 每按一次,删除光标所在位置的前面一个字符。
x: 每按一次,删除光标所在位置的后面一个字符。
#x: 删除光标所在位置后面6个字符。
d^: 删至行首。
d$: 删至行尾。
dd:(剪切)删除光标所在行。
dw: 删除一个单词/光标之后的单词剩余部分。
d4w: 删除4个word。
#dd: 从光标所在行开始删除#行。
daB: 删除{}及其内的内容。
diB: 删除{}中的内容。
n1,n2 d:将n1,n2行之间的内容删除。
4. 查找:
/: 输入关键字,发现不是要找的,直接在按n,向后查找直到找到为止。
?: 输入关键字,发现不是要找的,直接在按n,向前查找直到找到为止。
*: 在当前页向后查找同一字。
#: 在当前页向前查找同一字。
5. 复制 y(yank)为主:
yw: 将光标所在之处到字尾的字符复制到缓冲区中。
#yw: 复制#个字到缓冲区。
Y:相当于yy, 复制整行。
#yy:表示复制从光标所在的该行往下数#行文字。
p: 粘贴。所有与y相关的操作必用p来结合粘贴。
n1,n2 co n3:复制第n1行到第n2行之间的内容到第n3行后面。
6. 大小写转换:
gUU: 将当前行的字母改为大写。
guu: 将当前行的字母改为小写。
gUw: 将当前光标下的单词改为大写。
guw: 将当前光标下的单词改为小写。
a. 整篇大写:
ggguG
gg: 光标到文件第一个字符。
gu: 把选择范围全部小写。
G: 到文件结束。
b. 整篇小写:gggUG
7. 其它:
J:当前行和下一行合并成一行。
8. 移动:
n1,n2 m n3:将n1行到n2行之间的内容移至n3行下。
Errors
The Kittn API uses the following error codes:
Error Code | Meaning |
---|---|
400 | Bad Request – Your request sucks |
401 | Unauthorized – Your API key is wrong |
403 | Forbidden – The kitten requested is hidden for administrators only |
404 | Not Found – The specified kitten could not be found |
405 | Method Not Allowed – You tried to access a kitten with an invalid method |
406 | Not Acceptable – You requested a format that isn’t json |
410 | Gone – The kitten requested has been removed from our servers |
418 | I’m a teapot |
429 | Too Many Requests – You’re requesting too many kittens! Slow down! |
500 | Internal Server Error – We had a problem with our server. Try again later. |
503 | Service Unavailable – We’re temporarially offline for maintanance. Please try again later. |
Golang
The Design
Go 看上去是 C 而不是 C++。我自己在学习和项目使用时,并没有很在意 C 和 C++ 的区别,但看到 Go 时,发现了许多不同。
package foobar
type Bar struct {
n int
}
func (b *Bar) foo() int {
return b.n
}
Go 语言在设计中的感觉是追求显式表达,避免隐式表达。
class Bar(object):
n = "a local variable"
def __init__(self):
self.n = 0
def foo(self):
pass
Python 中也有相似的地方,在类中显式地写出self
,对self
的操作才是对对象的操作。
但 Go 中向对象增加方法method
也要显式地写出对象。
Go 中不支持参数默认值,函数重载。C 也不支持,是 C++ 支持。
参数默认值其实是一种隐式表达。调用者仅看名字,不去查看默认参数时,有时会遇到参数默认值导致问题,给调试带来不少麻烦。
一个表达的意义应该是唯一的,没有二义性。凡是可能导致二义性的行为都应是禁止或尽量避免的。比如函数默认参数,比如类的默认拷贝构造函数。还有一个书写方便但让项目更难调试的功能:自动类型转换。
进一步,一种表达的写法也应尽可能是唯一的。在 Go 语言的写法中,就没有单行 if
语句要不要加括号/braces 这样的问题。if else
中 else
的位置也是固定的。另一方面,你可以用无条件的 switch
来更清楚地表达 if-then-else
。
引用某匿名用户的话:“语法糖好吃不一定有益。”
Object Orientation
Golang 中没有 class
。它的面向对象的实现,可以用 struct
来做。
type Record struct {
Title string
Dept string
value int
}
Golang 中变量和函数首字母大写相当于 public
, 小写相当于 private
,在包和结构体中都是如此。
func (r Record) Publish() { fmt.Println(r.Title) }
func (r *Record) setTitle(s string) { r.Title = s }
Golang 中结构体的方法声明与众不同,是普通函数的方式,并在 func
后加上函数操作的对象,对象加 *
则传指针,不加时传值。
record := &Record{}
record.Title = "New Time"
record2 := &Record{Title: "Two"}
record3 := new(Record)
record3.Title = "Three"
record4 := Record{}
record4.Title = "Four"
record5 := Record{Title: "Five"}
对象实例化。加 &
和 new
时,生成指针对象。一般当对象较小时,传值较好;对象较大时,传指针较好。
func NewRecord(param string, p ...interface{}) *Record
func NewRecord(title string) (r *Record) {
r = &Record{}
r.Title = title
return
}
record := NewRecord("Initialized")
Golang 并没有构造函数,一般也不要用。如果想在初始化时额外做些事的话,要自己写方法来生成实例。
type Record struct {
Title string
Dept string
value int
}
type Archive struct {
Record
Dept string
Location string
}
archive := &Archive{}
archive.Title = "Archive"
archive.Dept = "A-20"
archive.Record.Dept = "Center"
// partial print
&{{ Center } A-20 }
Golang 的继承/组合,cmoposition。在属性冲突时,使用 Archive
中的,而 Record
中的用 archive.Record.Dept
访问。
Golang 不支持重载,重载属于 redeclared
错误,但可以使用不定参数来处理不同参数调用。
func (r *Record) bar(args ...interface{}) { fmt.Println(args) }
接口
TBC
Javascript
Thoughts
Why JavaScript
I use JavaScript to develop products because it seems faster and easier. Use web browsers as JVM, use DOM to build UI, and sue JS to implement business logic.
React
React provides a different way of organize frontend codes. And I like it very much.
Before writing WebApp, my programming practice uses Cpp mostly. So class and components are very handy, and very easy to understand. Different from the noodle of Vanilla JS in complex application, React divides the system to proper components, and makes the data flow in one direction only.
Since more services are moved to Cloud, there are more business-oriented applications which are more complex than client/consumer oriented apps. React can make some real differences in these fields.
Vanilla JS
Some time it seems Vanilla JS is not important, bacause lib like jQuery wrapped up many things. During the early stage of developing an app, you might need to find certain libs to solve the problems you are facing. When you put those codes together, you might encounter problems, and to handle those problems, Vanilla JS is very helpful in locating the problem and finger out what’s wrong.
Another thing is if you need to glue some modules together, sometimes you need to use Vanilla JS to do the job, instead of find another lib to glue modules.
React createClass vs Component
ES6 class might be just a syntactic sugar instead of real class, but things are headed towards this way. I choose to write my code the ES6 way when it’s convenient. As to Component, it’s neater than createClass. No trailing comma, less extra parentheses, and easier to read.
Introduction
Approaches to Create Objects
let str = 'direct assigned';
let newstr = new String("String instance initialization")
let fn = new Function("arg_name", "console.log(arg_name)");
let obj = new Object();
- Inner Object
JS has two types of inner object:
- original object, eg:
Function, Object, String
- run-time object / host object, eg:
window, document
- original object, eg:
let foo = {k:'record', v:[1,2,3]}
let bar = new Object();
bar.k = 'record';
- JSON Object
JS can creates objects via JSON objects.
And JS can turn Object instance to JSON object.
function Foo() { this.k = 'record'; this.v = 1234; }
function Bar() {}
Bar.prototype.k = 'record';
Bar.prototype.v = 1234;
- Customized Object
Function
can be used to create customized object.- use
this
the attributes and methods are initialized for every instance - use
prototype
the attributes and methods are only references on every instance
- use
Number
Integer
环境 64位 Linux, nodejs v7.2.1
- 16位数 可以正确显示
- 17位数 17位上开始失准; 然后归双; 然后舍入, 17位为0
- 18-22位数 16位后直接丢失, 变为0
- 23位以上, 变为浮点数, 进行 parseInt() 会出错
delete operator in JS
The delete operator removes a property from an object.
delete expression
where expression
should evaluate to a property reference.
delete object.property
delete object['property']
Parameters
object The name of an object, or an expression evaluating to an object.
property The property to delete.
N.B., the delete operator has nothing to do with directly freeing memory. Memory management is done indirectly via breaking references.
- delete will return true on successful deletion, or it will false or raises a SyntaxError or ReferenceError in strict mode.
- Notice that delete will also return true if the target property does not exist and delete has no effect.
- Delete can only remove own properties. If the prototype chain has a property with the same name, then the object will use it.
- An property declared with var cannot be deleted from global/function scope.
- function can only be deleted if it’s a part of an object.
- An property declared with let or const cannot be deleted from their scope.
- Non-configurable properties cannot be deleted, which includes properties of built-in objects like Math, Array, Object, and properties created by Objects.defineProperty().
Array
Array Loop
let arr = [];
for (let i in arr) ;
for (let i of arr) ;
arr.forEach((e) => {});
arr.map((e) => {return e});
arr.every((e) => {return e});
arr.some((e) => {return e});
JS Development Environment
随着前端和 Node.JS 的发展, 以及页面的APP化, JS 成为开发应用的不错的选择. 前后通吃, 语法灵活, 发展迅速, 对提高开发效率很有帮助.
JS 开发发展迅速带来的一个问题是工具和库众多, 开发中选择什么工具成了一个问题. 我从两年前真正开始写JS开始尝试了一些工具和库, 试过之后才知道什么更适合自己.
编辑器
单就文本编辑器而言, 当然还是首推 Emacs 和 Vim. 除这两者之外, 还有许多不错和编辑器和IDE.
- Sublime 比较灵活, 有插件, 可配置. 上手容易, 但要用上各种插件有点繁.
- Atom 比较适合前端技术栈, github 出品, 应该不错. 没真正用过.
- Brackets 使用时感觉很方便, web 预览功能很不错. 当时版本较早, 有一些bug, 现在应该更好用了.
- WebStorm 完整的 IDE 的样子, 对 JS 开发非常不错, 前端, Node.JS, git 都支持, 也有 emacs 模式. 用习惯之后对生产效率应该会有提升.
打包
打包包括了开发上线所需的预编译, 压缩, 转换, 优化, 构建任务.
现在使用的是 Webpack. 既然 Webpack 有丰富的loader, 方便的 API, 良好的生态环境, 为什么不用它呢?
由于接触JS工程比较晚, 有 Webpack 可用, 对早期的 Grunt 和后来的 Gulp 并不了解. 大家似乎是觉得 Grunt 配置麻烦, 效率不高, 于是转向 Gulp. 还有就是使用 NPM 的构建方式, 我不怎么会用, 感觉和上面三种打包工具不属一类工具. 在自己的工程中尝试使用过 Grunt 和 Gulp, 由于工程较小, 使用起来并没有感觉方便. 在压缩js, 分发文件方面, 比自己写 Python 脚本更复杂.
Grunt 要完成配置之后, 在日常开发中, 通过一个命令就能自动完成文件合并压缩, 上线构建, 在线重载(live-reload). 在项目变得复杂之后, 仅靠配置难以满足需求, 只有增加插件配置项. 而越来越多的配置项使得Grunt的配置变得过于麻烦.
Gulp 则是提供类 Linux 的文件流操作, 以编程的方式组建任务, 来解决越来越多的配置项的问题.
webpack + vue 心得
(ref: wechat 医小生与程序猿 (doctor_programmer))
要单页面还是多页面。我们的项目比较大,目录又多又深,如果用配合vue-router做成整体的单页面应用,必然会因多级嵌套路由而造成各种困扰。所以最终决定一个功能模块一个页面,各模块自己做成SPA。这个方案也为后面进行多入口打包带来了麻烦。
公共框架如何处理。 所谓公共框架就是像vue、jquery、bootstrap这的第三方框架。 它们基本不会改动,所以肯定是要单独打成一个包的,充分利用缓存。另外一个问题就是,这些框架的代码放哪里好,有人用bower管理,有人用npm管理,或者是像我们之前那样在项目中放一个lib目录,下载好框架代码后就放进去不动了。不过现在业界普遍都推commonjs规范了,所以用npm来管理是个趋势。
项目的公用组件如何打包。 既然是用vue,那肯定会写很多公共的组件了,这些组件肯定不能和第三方库打包到一起,也不要和业务代码打包在一起,因为业务代码总改动,而公共组件相对改动也比较少。最好是把所有的组件打成一个包。然而webpack的设计思想并不是这样,它是完全按照模块的依赖树来分析,根据依赖进行打包。我们的组件之间如果没有依赖,是没法打到一起的。
CommonsChunkPlugin的硬伤。 关于公用组件的处理,其实可以用CommonsChunkPlugin这个插件,它能自动分析出公共模块并打成一个包。但是这个插件有个硬伤,如果我写了一个名为popbox的组件,本意是想给项目公用,但是目前只有一个模块用到了它,那么插件并不能知道它是公用插件,而会把它和业务代码打包到一起。倘若将来有第二个模块用到了popbox,就又会把它打进公共包里,这显然是很弱智的行为。
entry只干入口的事。 我一开始想,把公共组件放entry中打包一下行不行,像这样配置:
components: ['loading', 'menu', 'box']
。结果证明是不能的,文件虽然能打包出来,但是没法用,就是你<script>
标签把文件加到页面也不行,别的模块用require无法引用到。原因就是entry打包出的文件作为入口文件,必须包含直接运行的代码。webpack-stream是个鸡肋。 一开始我被entry和output的各种配置整的摸不着头脑,因为项目目录多,想要更精细的控制,却发现output总是无法按我的需求输出。后来我看到了有webpack-stream这个东西,而且是官方推荐的。简单来讲,它就是实现了文件流接口,从而能与gulp配合工作,我一看这是个好东西啊。研究了一番,发现也没什么功能,就是能用gulp.src代替entry,用gulp.dest代替output,对于多入口打包,就完全没什么用了,还得在webpack中配置,所以简直就是个鸡肋。直接弃用。
这么多require用哪个。 一开始用webpack构建应用的时候,每当写require的时候我是懵逼的,有commonjs风格的require用法、AMD风格的require加回调用法,以及webpack提供的require.ensure用来打包异步加载的模块。另外还有ES6的import,既然都用vue了,我们肯定得用ES6嘛。这么多引入模块的方法,你得保持头脑清醒了,他们都有什么区别,什么场景下用哪个。
dist目录不是给人看的。 鉴于之前用gulp的习惯,打包后的目录也是有很清晰的结构,而且打包前我们src目录下的代码也是直接可运行的。但是有了webpack之后,首先src下的代码未经打包不能在开发环境运行,其次output选项只能指定一个输出目录,无法再按你的想法再进行组织。唯一有点用的方法是在entry中,把入口文件名字写成这样foo/bar/baz。勉强能在输出目录中新建出文件夹。但是总体来看,dist目录还是一团糟,尤其是异步的chunk文件,只能是id+hash这样的名字。所以我也明白了,要用webpack就别打算去看dist目录了,只能用sourcemap在浏览器看。
Core Features of ES2015 (ES6)
Note on Adrain Mejia’s article
History
ES6, a.k.a. ES2015+, ECMAScript6.
Javascript is evolving since it was created, and ES6 is now well supported in all modern browsers.
ES6 core features
block-scoped variables
We know that var is not block-scoped, but function scoped. In ES6, let and const is used instead of var to limit variable scoped to block. let is the better var A variable must be defined before use when it’s defined by let, and it exists only inside its block, like a for-loop or if-block.
Hoist
var x = 'outer';
function test(inner) {
if (inner) {
var x = 'inner';
return x;
}
return x;
}
test(false);
// <- undefined
Using var, even the if
clause was not executed, the x
inside it was hoisted. But only the declaration was hoisted, not the initialization.
Temporal Dead-zone
In ES6, let hoists a variable to the top of the block, and from the top on the block to the let declaration is a “temporal dead zone”, where a variable is not usable (ReferenceError).
No more IIFE
We all familiar with IIFE (Immediately-Invoked Function Expression), and write it everyday. Because without IIFE, the function names and variables will pollute everywhere.
(function() {
var bar = 'ES5';
function foo() {}
}());
With let grammar, IIFE is not essential anymore.
{
let bar = 'ES6';
let foo = () => {};
}
const
When a re-assignation is necessary, ust let. When the value of a variable should not be changable, use const.
const _version = '0.1';
Text Template
// ES5
var firstName = 'Adrian';
var lastName = 'Mejia';
console.log('Your name is ' + firstName + ' ' + lastName + '.');
Now ` and ${} is available.
// ES6
const firstName = 'Adrian';
const lastName = 'Mejia';
console.log(`Your name is ${firstName} ${lastName}`);
Multi-line String
// ES5
var template = '<li *ngFor="let todo of todos" [ngClass]="{completed: todo.isDone}" >n' +
' <div class="view">n' +
' <input class="toggle" type="checkbox" [checked]="todo.isDone">n' +
' <label></label>n' +
' <button class="destroy"></button>n' +
' </div>n' +
' <input class="edit" value="">n' +
'</li>';
console.log(template);
Just using `, you can write multiple string directly.
// ES6
const template = `<li *ngFor="let todo of todos" [ngClass]="{completed: todo.isDone}" >
<div class="view">
<input class="toggle" type="checkbox" [checked]="todo.isDone">
<label></label>
<button class="destroy"></button>
</div>
<input class="edit" value="">
</li>`;
console.log(template);
Deconstruction and Assignment
Get array elements.
// ES5
var array = [1, 2, 3, 4];
var first = array[0];
var third = array[2];
console.log(first, third); // 1 3
// ES6
const array = [1, 2, 3, 4];
const [first, ,third] = array;
console.log(first, third); // 1 3
Switch values.
// ES5
var a = 1;
var b = 2;
var tmp = a;
a = b;
b = tmp;
console.log(a, b); // 2 1
// ES6
let a = 1;
let b = 2;
[a, b] = [b, a];
console.log(a, b); // 2 1
Return multiple value
// ES5
function margin() {
var left=1, right=2, top=3, bottom=4;
return { left: left, right: right, top: top, bottom: bottom };
}
var data = margin();
var left = data.left;
var bottom = data.bottom;
console.log(left, bottom); // 1 4
// ES6
function margin() {
const left=1, right=2, top=3, bottom=4;
return { left, right, top, bottom }; // grammar sugar {left:left} -> {left}
}
const { left, bottom } = margin();
console.log(left, bottom); // 1 4
Parameter match
// ES5
var user = {firstName: 'Adrian', lastName: 'Mejia'};
function getFullName(user) {
var firstName = user.firstName;
var lastName = user.lastName;
return firstName + ' ' + lastName;
}
console.log(getFullName(user)); // Adrian Mejia
// ES6
const user = {firstName: 'Adrian', lastName: 'Mejia'};
function getFullName({ firstName, lastName }) {
return `${firstName} ${lastName}`;
}
console.log(getFullName(user)); // Adrian Mejia
Deep match
// ES5
function settings() {
return { display: { color: 'red' }, keyboard: { layout: 'querty'} };
}
var tmp = settings();
var displayColor = tmp.display.color;
var keyboardLayout = tmp.keyboard.layout;
console.log(displayColor, keyboardLayout); // red querty
// ES6
function settings() {
return { display: { color: 'red' }, keyboard: { layout: 'querty'} };
}
const { display: { color: displayColor }, keyboard: { layout: keyboardLayout }} = settings();
console.log(displayColor, keyboardLayout); // red querty
Deconstruction is useful, and helpful in forming a good coding style. Use array deconstrunction to avoid temporary variables. Use Object deconstrunction to deal with multiple return values.
Class and Object
// ES5
var Animal = (function () {
function MyConstructor(name) {
this.name = name;
}
MyConstructor.prototype.speak = function speak() {
console.log(this.name + ' makes a noise.');
};
return MyConstructor;
})();
var animal = new Animal('animal');
animal.speak(); // animal makes a noise.
ES6 provides grammar sugar to define a class object. With class, constructor, you can define speak() instead of constructor.prototype.speak = function(). Use class and avoid deal with prototype. The same functionality, more elegant.
// ES6
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(this.name + ' makes a noise.');
}
}
const animal = new Animal('animal');
animal.speak(); // animal makes a noise.
Avoid empty constructor. A class has a default constructor itself.
Inherit
Inheritance in ES5 is complex.
// ES5
var Lion = (function () {
function MyConstructor(name){
Animal.call(this, name);
}
// prototypal inheritance
MyConstructor.prototype = Object.create(Animal.prototype);
MyConstructor.prototype.constructor = Animal;
MyConstructor.prototype.speak = function speak() {
Animal.prototype.speak.call(this);
console.log(this.name + ' roars ');
};
return MyConstructor;
})();
var lion = new Lion('Simba');
lion.speak(); // Simba makes a noise.
// Simba roars.
// ES6
class Lion extends Animal {
speak() {
super.speak();
console.log(this.name + ' roars ');
}
}
const lion = new Lion('Simba');
lion.speak(); // Simba makes a noise.
// Simba roars.
ES6 has extends and super, makes inheritance much clearer.
Promise
promise is used to eliminate the callback hell.
// ES5
function printAfterTimeout(string, timeout, done){
setTimeout(function(){
done(string);
}, timeout);
}
printAfterTimeout('Hello ', 2e3, function(result){
console.log(result);
// nested callback
printAfterTimeout(result + 'Reader', 2e3, function(result){
console.log(result);
});
});
This function exec a callback after done(), twice. More layer of callbacks, it will become a mess. With promise:
// ES6
function printAfterTimeout(string, timeout){
return new Promise((resolve, reject) => {
setTimeout(function(){
resolve(string);
}, timeout);
});
}
printAfterTimeout('Hello ', 2e3).then((result) => {
console.log(result);
return printAfterTimeout(result + 'Reader', 2e3);
}).then((result) => {
console.log(result);
});
Use promise, another function can be called by then after a function is executed without nesting.
Arrow Function
In ES5, this point to the environment in which a function is executed, instead of in which it is defined.
// ES5
var _this = this; // need to hold a reference
$('.btn').click(function(event){
_this.sendData(); // reference outer this
});
$('.input').on('change',function(event){
this.sendData(); // reference outer this
}.bind(this)); // bind to outer this
With ()=>{}
, you don’t need to use a temporary variable to hold this or use bind.
// ES6
// this will reference the outer one
$('.btn').click((event) => this.sendData());
// implicit returns
const ids = [291, 288, 984];
const messages = ids.map(value => `ID is ${value}`);
for … of
default value of function arguments
Just like in C, Python, and any other languages.
… remaining arguments
A argumnets like operation.
function printf(format, ...params) {
console.log('params: ', params);
console.log('format: ', format);
}
printf('%s %d %.2f', 'adrian', 321, Math.PI);
open/spread operator (??? which terminology)
In ES5, apply()
Math.max(2,100,1,6,43) // <- 100
Math.max([2,100,1,6,43]) // <- NaN
Math.max.apply(Math, [2,100,1,6,43]) // <- 100
In ES6, just spread the array.
Math.max(...[2,100,1,6,43]) // 100
In ES6, concat() arrays.
var array1 = [2,100,1,6,43];
var array2 = ['a', 'b', 'c', 'd'];
var array3 = [false, true, null, undefined];
console.log(array1.concat(array2, array3));
ES6 spreads nested arrays.
// ES6
const array1 = [2,100,1,6,43];
const array2 = ['a', 'b', 'c', 'd'];
const array3 = [false, true, null, undefined];
console.log([...array1, ...array2, ...array3]);
Note on Express Basic
Routing
A route method is derived from one of the HTTP methods, and is attached to an instance of the express class.
Route methods
Express supports the following routing methods that correspond to HTTP methods:
get, post, put, head, delete, options, trace, copy, lock, mkcol, move, purge, propfind, proppatch, unlock, report, mkactivity, checkout, merge, m-search, notify, subscribe, unsubscribe, patch, search, and connect
.
To route methods that translate to invalid JavaScript variable names, use the bracket notation.
app['m-search']('/', function(){})
An extra routing method app.all()
is used for loading middleware functions at a path for all request methods.
app.get('/', function(req, res) {
})
app.post('/', function(req, res) {
})
app.all('/', function(req, res, next) {
;
next()
})
Route paths
Route path supports string, string pattern with *? + * ()*, and regular expression. In string, hyphen and dot are literal characters.
app.get('/ab*c(de)?f', function(req, res) {})
app.get(/.*foo$/, function(req, res) {})
Route parameters
Express captures named URL segments into req.params object. > The name of route parameters must be made up of “word characters” ([A-Za-z0-9_]).
Route path: /users/:userId/books/:bookId
Request URL: http://localhost:3000/users/34/books/8989
req.params: { "userId": "34", "bookId": "8989" }
Route path: /flights/:from-:to
Request URL: http://localhost:3000/flights/LAX-SFO
req.params: { "from": "LAX", "to": "SFO" }
Route path: /plantae/:genus.:species
Request URL: http://localhost:3000/plantae/Prunus.persica
req.params: { "genus": "Prunus", "species": "persica" }
Route handlers
Route handlers are used to handle a request, and they’re chained by next()
.
You can use functions, and arrays of functions, and their mix.
var cb0 = function (req, res, next) {
console.log('CB0')
next()
}
var cb1 = function (req, res, next) {
console.log('CB1')
next()
}
app.get('/example', [cb0, cb1], function (req, res, next) {
console.log('the response will be sent by the next function ...')
next()
}, function (req, res) {
res.send('Hello from D!')
})
Response methods
A response method sends a response to the client, and terminates the request-response cycle. If no response method is called, the client request will be left hanging.
- **Method** **Description**
- res.download() Prompt a file to be downloaded.
- res.end() End the response process.
- res.json() Send a JSON response.
- res.jsonp() Send a JSON response with JSONP support.
- res.redirect() Redirect a request.
- res.render() Render a view template.
- res.send() Send a response of various types.
- res.sendFile() Send a file as an octet stream.
- res.sendStatus() Set the response status code and send its string representation as the response body.
app.route()
It creates a handler-chain for a given route path.
app.route('/book')
.get(function (req, res) {
res.send('Get a random book')
})
.post(function (req, res) {
res.send('Add a book')
})
.put(function (req, res) {
res.send('Update the book')
})
express.Router
Use the express.Router class to create modular, mountable route handlers. A Router instance is a complete middleware and routing system; for this reason, it is often referred to as a “mini-app”.
The following example creates a router as a module, loads a middleware function in it, defines some routes, and mounts the router module on a path in the main app.
Create a router file named birds.js in the app directory, with the following content:
var express = require('express')
var router = express.Router()
// middleware that is specific to this router
router.use(function timeLog (req, res, next) {
console.log('Time: ', Date.now())
next()
})
// define the home page route
router.get('/', function (req, res) {
res.send('Birds home page')
})
// define the about route
router.get('/about', function (req, res) {
res.send('About birds')
})
module.exports = router
Then, load the router module in the app:
var birds = require('./birds')
// ...
app.use('/birds', birds)
The app will now be able to handle requests to /birds and /birds/about, as well as call the timeLog middleware function that is specific to the route.
MySQL in NodeJS
1. 建立数据库连接 createConnection(Object)
该方法接受一个对象作为参数,该对象有四个常用的属性host,user,password,database。与php中链接数据库的参数相同。属性列表如下:
host: 连接数据库所在的主机名. (默认: localhost)
port: 连接端口. (默认: 3306)
localAddress: 用于TCP连接的IP地址. (可选)
socketPath: 链接到unix域的路径。在使用host和port时该参数会被忽略.
user: MySQL用户的用户名.
password: MySQL用户的密码.
database: 链接到的数据库名称 (可选).
charset: 连接的字符集. (默认: 'UTF8_GENERAL_CI'.设置该值要使用大写!)
timezone: 储存本地时间的时区. (默认: 'local')
stringifyObjects: 是否序列化对象. See issue #501. (默认: 'false')
insecureAuth: 是否允许旧的身份验证方法连接到数据库实例. (默认: false)
typeCast: 确定是否讲column值转换为本地JavaScript类型列值. (默认: true)
queryFormat: 自定义的查询语句格式化函数.
supportBigNumbers: 数据库处理大数字(长整型和含小数),时应该启用 (默认: false).
bigNumberStrings: 启用 supportBigNumbers和bigNumberStrings 并强制这些数字以字符串的方式返回(默认: false).
dateStrings: 强制日期类型(TIMESTAMP, DATETIME, DATE)以字符串返回,而不是一javascript Date对象返回. (默认: false)
debug: 是否开启调试. (默认: false)
multipleStatements: 是否允许在一个query中传递多个查询语句. (Default: false)
flags: 链接标志.
// 还可以使用字符串连接数据库
var connection = mysql.createConnection('mysql://user:pass@host/db?debug=true&charset=BIG5_CHINESE_CI&timezone=-0700');
2 结束数据库连接 end() destroy()
end()接受一个回调函数,并且会在query结束之后才触发,如果query出错,仍然会终止链接,错误会传递到回调函数中处理。
destroy()立即终止数据库连接,即使还有query没有完成,之后的回调函数也不会在触发。
3. 创建连接池 createPool(Object)
Object和createConnection参数相同 可以监听connection事件,并设置session值
pool.on('connection', function(connection) {
connection.query('SET SESSION auto_increment_increment=1')
});
connection.release()释放链接到连接池。如果需要关闭连接并且删除,需要使用connection.destroy()
pool除了接受和connection相同的参数外,还接受几个扩展的参数
createConnection: 用于创建链接的函数. (Default: mysql.createConnection)
waitForConnections: 决定当没有连接池或者链接数打到最大值时pool的行为. 为true时链接会被放入队列中在可用是调用,为false时会立即返回error. (Default: true)
connectionLimit: 最大连接数. (Default: 10)
queueLimit: 连接池中连接请求的烈的最大长度,超过这个长度就会报错,值为0时没有限制. (Default: 0)
4. 连接池集群
允许不同的host链接
// create
var poolCluster = mysql.createPoolCluster();
poolCluster.add(config); // anonymous group
poolCluster.add('MASTER', masterConfig);
poolCluster.add('SLAVE1', slave1Config);
poolCluster.add('SLAVE2', slave2Config);
// Target Group : ALL(anonymous, MASTER, SLAVE1-2), Selector : round-robin(default)
poolCluster.getConnection(function (err, connection) {});
// Target Group : MASTER, Selector : round-robin
poolCluster.getConnection('MASTER', function (err, connection) {});
// Target Group : SLAVE1-2, Selector : order
// If can't connect to SLAVE1, return SLAVE2. (remove SLAVE1 in the cluster)
poolCluster.on('remove', function (nodeId) {
console.log('REMOVED NODE : ' + nodeId); // nodeId = SLAVE1
});
poolCluster.getConnection('SLAVE*', 'ORDER', function (err, connection) {});
// of namespace : of(pattern, selector)
poolCluster.of('*').getConnection(function (err, connection) {});
var pool = poolCluster.of('SLAVE*', 'RANDOM');
pool.getConnection(function (err, connection) {});
pool.getConnection(function (err, connection) {});
// destroy
poolCluster.end();
链接集群的可选参数
canRetry: 值为true时,允许连接失败时重试(Default: true)
removeNodeErrorCount: 当连接失败时 errorCount 值会增加. 当errorCount 值大于 removeNodeErrorCount 将会从PoolCluster中删除一个节点. (Default: 5)
defaultSelector: 默认选择器. (Default: RR)
RR: 循环. (Round-Robin)
RANDOM: 通过随机函数选择节点.
ORDER: 无条件地选择第一个可用节点.
5. 切换用户 / 改变连接状态
Mysql允许在比断开连接的的情况下切换用户
connection.changeUser({user : 'john'}, function(err) {
if (err) throw err;
});
// 参数
user: 新的用户 (默认为早前的一个).
password: 新用户的新密码 (默认为早前的一个).
charset: 新字符集 (默认为早前的一个).
database: 新数据库名称 (默认为早前的一个).
6. 处理服务器连接断开
var db_config = {
host: 'localhost',
user: 'root',
password: '',
database: 'example'
};
var connection;
function handleDisconnect() {
connection = mysql.createConnection(db_config); // Recreate the connection, since
// the old one cannot be reused.
connection.connect(function(err) { // The server is either down
if(err) { // or restarting (takes a while sometimes).
console.log('error when connecting to db:', err);
setTimeout(handleDisconnect, 2000); // We introduce a delay before attempting to reconnect,
} // to avoid a hot loop, and to allow our node script to
}); // process asynchronous requests in the meantime.
// If you're also serving http, display a 503 error.
connection.on('error', function(err) {
console.log('db error', err);
if(err.code === 'PROTOCOL_CONNECTION_LOST') { // Connection to the MySQL server is usually
handleDisconnect(); // lost due to either server restart, or a
} else { // connnection idle timeout (the wait_timeout
throw err; // server variable configures this)
}
});
}
handleDisconnect();
7. 转义查询值
为了避免SQL注入攻击,需要转义用户提交的数据。可以使用connection.escape() 或者 pool.escape()
如
var userId = 'some user provided value';
var sql = 'SELECT * FROM users WHERE id = ' + connection.escape(userId);
connection.query(sql, function(err, results) {
// ...
});
或者使用?作为占位符
connection.query('SELECT * FROM users WHERE id = ?', [userId], function(err, results) {
// ...
});
不同类型值的转换结果
Numbers 不变
Booleans 转换为字符串 'true' / 'false'
Date 对象转换为字符串 'YYYY-mm-dd HH:ii:ss'
Buffers 转换为是6进制字符串
Strings 不变
Arrays => ['a', 'b'] 转换为 'a', 'b'
嵌套数组 [['a', 'b'], ['c', 'd']] 转换为 ('a', 'b'), ('c', 'd')
Objects 转换为 key = 'val' pairs. 嵌套对象转换为字符串.
undefined / null ===> NULL
NaN / Infinity 不变. MySQL 不支持这些值, 除非有工具支持,否则插入这些值会引起错误.
转换实例:
var post = {id: 1, title: 'Hello MySQL'};
var query = connection.query('INSERT INTO posts SET ?', post, function(err, result) {
// Neat!
});
console.log(query.sql); // INSERT INTO posts SET `id` = 1, `title` = 'Hello MySQL'
或者手动转换
var query = "SELECT * FROM posts WHERE title=" + mysql.escape("Hello MySQL");
console.log(query); // SELECT * FROM posts WHERE title='Hello MySQL'
8. 转换查询标识符
如果不能信任SQL标识符(数据库名、表名、列名),可以使用转换方法mysql.escapeId(identifier);
var sorter = 'date';
var query = 'SELECT * FROM posts ORDER BY ' + mysql.escapeId(sorter);
console.log(query); // SELECT * FROM posts ORDER BY `date`
支持转义多个
var sorter = 'date';
var query = 'SELECT * FROM posts ORDER BY ' + mysql.escapeId('posts.' + sorter);
console.log(query); // SELECT * FROM posts ORDER BY `posts`.`date`
可以使用??作为标识符的占位符
var userId = 1;
var columns = ['username', 'email'];
var query = connection.query('SELECT ?? FROM ?? WHERE id = ?', [columns, 'users', userId], function(err, results) {
// ...
});
console.log(query.sql); // SELECT `username`, `email` FROM `users` WHERE id = 1
9. 准备查询
可以使用mysql.format来准备查询语句,该函数会自动的选择合适的方法转义参数。
var sql = "SELECT * FROM ?? WHERE ?? = ?";
var inserts = ['users', 'id', userId];
sql = mysql.format(sql, inserts);
10、自定义格式化函数
connection.config.queryFormat = function (query, values) {
if (!values) return query;
return query.replace(/\:(\w+)/g, function (txt, key) {
if (values.hasOwnProperty(key)) {
return this.escape(values[key]);
}
return txt;
}.bind(this));
};
connection.query("UPDATE posts SET title = :title", { title: "Hello MySQL" });
11. 获取插入行的id
当使用自增主键时获取插入行id,如:
connection.query('INSERT INTO posts SET ?', {title: 'test'}, function(err, result) {
if (err) throw err;
console.log(result.insertId);
});
12. 流处理
有时你希望选择大量的行并且希望在数据到达时就处理他们,你就可以使用这个方法
var query = connection.query('SELECT * FROM posts');
query
.on('error', function(err) {
// Handle error, an 'end' event will be emitted after this as well
})
.on('fields', function(fields) {
// the field packets for the rows to follow
})
.on('result', function(row) {
// Pausing the connnection is useful if your processing involves I/O
connection.pause();
processRow(row, function() {
connection.resume();
});
})
.on('end', function() {
// all rows have been received
});
13. 多语句/混合查询
因为混合查询容易被SQL注入攻击,默认是不允许的,可以使用var connection = mysql.createConnection({multipleStatements: true});开启该功能。 混合查询实例:
connection.query('SELECT 1; SELECT 2', function(err, results) {
if (err) throw err;
// `results` is an array with one element for every statement in the query:
console.log(results[0]); // [{1: 1}]
console.log(results[1]); // [{2: 2}]
});
// 同样可以使用流处理混合查询结果:
var query = connection.query('SELECT 1; SELECT 2');
query
.on('fields', function(fields, index) {
// the fields for the result rows that follow
})
.on('result', function(row, index) {
// index refers to the statement this result belongs to (starts at 0)
});
如果其中一个查询语句出错,Error对象会包含err.index指示错误语句的id,整个查询也会终止。 混合查询结果的流处理方式是做实验性的,不稳定。
14. 事务处理
connection级别的简单事务处理
connection.beginTransaction(function(err) {
if (err) { throw err; }
connection.query('INSERT INTO posts SET title=?', title, function(err, result) {
if (err) {
connection.rollback(function() {
throw err;
});
}
var log = 'Post ' + result.insertId + ' added';
connection.query('INSERT INTO log SET data=?', log, function(err, result) {
if (err) {
connection.rollback(function() {
throw err;
});
}
connection.commit(function(err) {
if (err) {
connection.rollback(function() {
throw err;
});
}
console.log('success!');
});
});
});
});
15. 错误处理
err.code = string
err.fatal => boolean
Object-Oriented Programming of Javascript
JS 的对象
JS 本身是基于对象的语言,在 JS 中所操作的基本都是对象。但在 ES2015 之前它又没有 class 的类。
如果要从原型对象生成实例对象,要把属性和方法封装到对象里。properties and methods
- 对象实例
let record = { name: "", nation: "" };
Schema 规格
let record1 = { name: "Alpha", nation: "China" };
let record2 = { name: "Beta, nation: "Denmark" };
给对象赋予属性和方法
这可以看作最简单的封装,但原型和实例间并不存在内在联系。
function Record(name, nation) {
return {
name: name,
nation: nation,
};
}
let record1 = Record("Alpha", "China");
let record2 = Record("Beta", "Denmark");
用函数调用返回对象方式可以在原型和实例间建立一定联系,但两个实例间没有内在联系。
- 构造函数模式
function Record(name, nation) {
this.name = name;
this.nation = nation;
}
let record1 = new Record("Alpha", "China");
let record2 = new Record("Beta", "Denmark");
Javascript 提供了 Constructor 构造函数模式。构造函数是使用了 this 变量的普通函数,在使用 new 运算符生成实例时,this 会绑定到对象实例上,使 foo.constructor == Foo
。JS 中的 instanceof 运算符可以验证原型与实例对象间的关系,(foo instanceof Foo) == true
。
- 原型模式 prototype
function Record(name, nation) { this.name = name; this.nation = nation; }
Record.prototype.species = "human";
Record.prototype.greet = function() { console.log("hello") };
let record1 = new Record("Alpha", "China"); // with `species` and `greet()`
let record2 = new Record("Beta", "Denmark");
构造函数模式的问题在于原型中的属性和方法在每个实例中都要创建。其中重复的内容就额外地占用的资源。JS 中每个构造函数都有 prototype 属性,指向另一个对象;所指向对象的属性和方法都被构造函数继承。
这样,不变的属性和方法可以直接定义在 prototype 对象上。在例子中,所有实例的 species 和 greet() 方法都指向相同的地址,即 prototype 对象。
JS 中有一些对应 prototype 模式的方法
isPrototypeOf()
判断 prototype 对象和实例间的关系
Record.prototype.isPrototypeOf(record1)
hasOwnProperty()
由实例判断一个属性是本地的还是继承的
record1.hasOwnProperty("name") == true
record1.hasOwnProperty("species") == false
in
可以遍历一个对象的所有属性,或一个实例是否含有某个属性
for (let prop in record) console.log(prop);
"name" in record1 == true
继承对象
function Record() { this.title = "Record" }
function Personnel(name, dept) { this.name = name; this.dept = dept; }
对象间继承的方法。
绑定构造函数
使用 apply / call 将父对象的构造函数绑定到子对象上。子对象中需要显式地加入绑定语句。
function Personnel(name, dept) { ...; Record.apply(this, arguments); }
prototype
将子类的 prototype 指向父类的一个实例,使所有子类实例继承父类。
Personnel.prototype = new Record();
Personnel.prototype.constructor = Personnel;
首先是将 Personnel.prototype 替换为指向 Record实例。这时 Personnel.prototype.constructor 指向了 Record 的构造函数,即Personnel.prototype.constructor == Record
,由 Personnel 创建的实例也是如此。所以要将 Personnel.prototype 重新指向 Personnel 的构造函数。继承 prototype
JS 的函数对象可以将不变属性放在 prototype 中,对于只使用不变量的子类,可以直接继承父类的 prototype。
function Record() {}
Record.prototype.title = "Record";
Personnel.prototype = Record.prototype;
Personnel.prototype.constructor = Personnel;
这种方法不需要创建父类的实例,效率更高一些。
直接继承 prototype 有一个问题,子类和父类的 prototype 指向了同一个对象,对子类 prototype 的修改会影响父类的 prototype。
Record.prototype.constructor == Personnel;
via 空对象
利用空对象占用内存空间较小这点,通过空对象实例完成继承。let F = function() {};
F.prototype = Record.prototype;
Personnel.prototype = new F();
Personnel.prototype.constructor = Personnel;
// YUI extend function
function extend(Child, Parent) {
let F = function() {};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
Child.uber = Parent.prototype;
}
extend(Personnel, Record);
可以对这个方式进行封装。如 YUI 库实现继承的方法。
- copy
就是将所有父类的属性和方法在子类中复制一份。
function Record() {}
Record.prototype.title = "Record";
function extend(Child, Parent)
for (let i in Parent.prototype)
Child.prototype[i] = Parent.prototype[i];
Child.prototype.uber = Parent.prototype;
}
一般对象继承
无构造函数的普通对象的继承。
let Record = {title: "Record"};
- 通过 object 函数
function object(obj) {
function F() {}
F.prototype = obj;
return new F();
}
let Personnel = object(Record);
- 浅复制
function extend(obj) {
let c = {};
for (let i in obj)
c[i] = obj[i];
c.uber = obj;
return c;
}
let Personnel = extend(Record);
浅复制在父对象的成员也是对象时,会把对象地址复制给子类,造成子类可以修改父类。
- 深复制
function deepCopy(Parent, Child) {
let Child = Child || {};
for (let i in Parent) {
if (typeof Parent[i] === "object") {
Child[i] = (Parent[i].constructor === Array) ? [] : {};
deepCopy(Parent[i], Child[i]);
} else {
Child[i] = Parent[i];
}
}
return Child;
}
Practice of JavaScript Performance
Since JavaScript becomes a fullstack programming language, in certain cases the performance of JS is critical. Here are some practice of JS, with some understanding of JS implementation.
Data Store
How a variable is create will effect its performance. - Use {} instead of new Object, use [] instead of new Array, use plain string instead of an object. - Use local temporary variable to store variable deep upon the data-chain, and values require many calculation. - dot-present has a similar performance as member operator, and it’s faster in Safari. object.name vs object[name], object.name is always faster.
Loop
In Chromium v54, for-in
is slower, and for (var i = 0; i < length; ++i)
, [].forEach(()=>{})
have similar performance. forEach is much faster than it was in earlier browser.
Event Delegate
It will cause too much loads and occupy too many memory binding the same event to massive elements in the page. It better to bind event to the outer layer, and deal with all events of its child elements.
document.getElementById('content').addEventListener('click', function(evt) {
evt = evt || window.event;
let target = evt.target || evt.srcElement;
if (target.nodeName.toLowerCase() !== 'a')
return;
console.log(target.href);
});
Re-Render
The source files create 2 trees in browser: DOM tree and Render tree. Render tree deal with DOM geometry attributions. When the style of DOM changes, browser re-render the DOM.
bodystyle = document.body.style;
bodystyle.color = red;
bodystyle.height = 1000px;
bodystyke.width = 100%;
Three times of modification will cause 3 re-renders. Under certain circumstances, reduce the times of modification will improve rendering performance.
bodystyle = document.body.style;
bodystyle.cssText 'color:red;height:1000px;width:100%';
JavaScript Load
<script> tag might block something from downloading, so it’s a good practice to put more script tag at the bottom of DOM, to avoid the time of empty page displayed to users.
JavaScript Deployment
Reduce the number of requirements and the file sizes will accelerate the page loading speed. For example, use webpack and uglify.
Cache JS Files
Cached files will improve the speed of page loading. Web servers use “Expires HTTP Resonse Header” to tell browser how long a resource should be cached. To avoid the problem of using old version of file, you can change the filename of static resource, like bundle-201612242359.js, using a timestamp to force browser to load the new js file.
An Introduction to Webpack
Webpack is a bundler for javascript, and related resources. All resources are treated as modules, and with various loaders, webpack can handle modules of CommonJs, AMD, ES6, CSS, Image, JSON, Coffeescript, LESS, etc.
// what a webpack config file 'webpack.config.js' looks like
module: {
loaders: [
{ test: /\.css$/, loader: 'style!css' }, // use ! to chain loaders
{ test: /\.js$/, loader: 'jsx?harmony!babel', exclude: /node_modules/ }
// loaders can be written like 'jsx-loader' or 'jsx'
// loaders can take parameters as a querystring
]
}
Entrance and output.
// webpack.config.js
module.exports = {
entry: './entry.js',
output: {
filename: 'bundle.js'
}
};
Filename extensions.
// webpack.config.js
module.exports = {
entry: 'entry.js',
output: {
filename: 'bundle.js'
},
resolve: {
extensions: ['', '.js]
// require('./foo') will search module with filename './foo' and './foo.js'
}
}
require a module
require('foo') // from module directory
require('./bar') // from relative path
define (require, exports.module) ->
require('foo')
require('./bar)
module shim
To require modules from window
, you might need expose-loader
.
Take jQuery for example
{ test: require.resolve('jquery'), loader: 'expose?jQuery' }
CSS and Image
Write loader and require them as normal modules.
// webpack.config.js
module.exports = {
entry: 'entry.js',
output: {
path: './build', // for output processed images and resources
publicPath: 'http://mycdn.com/', // for generate URLs to resources
filename: 'bundle.js'
},
module: {
loaders: [
{ test: /\.js$/, loader: 'jsx?harmony!babel', exclude: /node_modules/ }
{ test: /\.css$/, loader: 'style!css },
{ test: /\.less$/, loader: 'style!css!less },
{ test: /\.(png|jpg)$/, loader: 'url?limit=8192' }
// inline base64 URLs for <=8k images, direct URLs for the rest
]
}
};
require('./bootstrap.css');
require('./style.less');
let img = document.createElement('img');
img.src = require('./logo.png');
Multi-package and shared package
Shared packages are handled by commonsPlugin
.
var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin");
module.exports = {
entry: {
p1: "./page1",
p2: "./page2",
p3: "./page3"
},
output: {
filename: "[name].entry.chunk.js"
},
plugins: [
new CommonsChunkPlugin("commons.chunk.js")
]
}
Separated CSS package
Hash js files
One hash for the bundle
webpack ./entry output.[hash].bundle.js
{
output: {
path: path.join(__dirname, "assets", "[hash]"),
publicPath: "assets/[hash]/",
filename: "output.[hash].bundle.js",
chunkFilename: "[id].[hash].bundle.js"
}
}
One hash per chunk --output-chunk-file [chunkhash].js
output: { chunkFilename: "[chunkhash].bundle.js" }
Get filenames from stats
plugins: [
function() {
this.plugin("done", function(stats) {
require("fs").writeFileSync(
path.join(__dirname, "..", "stats.json"),
JSON.stringify(stats.toJson()));
});
}
]
Options for Product
You can specify a different config file for product-level bundling.
webpack --config webpack.min.js
Compress the code. Loaders are switched into minimizing mode. Uglify
plugins: [
new webpack.optimize.MinChunkSizePlugin(minSize)
]
new webpack.optimize.UglifyJsPlugin([options])
// e.g.
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})
You can/should use normal npm React package instead of the precompiled library. Webpack can compress it to smaller size than react.min.js.
new webpack.DefinePlugin({
"process.env": {
NODE_ENV: JSON.stringify("production")
}
})
Hot Replace
Include webpack/hot/dev-server
in your project code, edit config file.
entry: {
main: ['webpack/hot/dev-server', './entry],
},
Then start the server, and visit it at [http://localhost:8080/webpack-dev-server/].
webpack-dev-server --hot --quiet
And there is a react-hot-loader.
entry: [
'webpack-dev-server/client?http://0.0.0.0:8080', // WebpackDevServer host and port
'webpack/hot/only-dev-server',
'./scripts/index' // Your appʼs entry point
]
As for only-dev-server
, the issue is replied here.
Miscellaneous
When webpack reports an error, but you cannot find out what’s wrong, try this.
webpack --display-error-details
[Webpack list of loaders] http://webpack.github.io/docs/list-of-loaders.html optimization [long term caching]http://webpack.github.io/docs/long-term-caching.html
Another Webpack Note
其实Webpack的入门指导文章非常多,配置方式也各有各样,这里我推荐题叶大神的入门级指南–Webpack 入门指迷,如果不知道Webpack是什么或者不是很清楚各项配置含义的开发者,可以看此文章扫扫盲。毕竟我这篇文章并不是特别基础。
1. base.js
var path = require(‘path’) var baseConfig = { resolve: { extensions: [“, ‘.js’], fallback: [path.join(dirname, ‘../node_modules’)], alias: { ‘src’: path.resolve(dirname, ‘../src’), ‘assets’: path.resolve(dirname, ‘../src/assets’), ‘components’: path.resolve(dirname, ‘../src/components’) } }, module: { loaders: [{ test: /.js$/, loader: ‘babel’, exclude: /node_modules/ }, { test: /.(png|jpe?g|gif|svg|woff2?|eot|ttf|otf)(\?.*)?$/, loader: ‘url?limit=8192&context=client&name=[path][name].[hash:7].[ext]’ }, { test: /.css$/, loader: ‘style!css!autoprefixer’, }, { test: /.scss$/, loader: ‘style!css!autoprefixer!sass’ }] } };
module.exports = baseConfig;
` 解读下这个基本配置:
1、resolve 解析模块依赖的时候,受影响的配置项。
extensions 决定了哪些文件后缀在引用的时候可以省略点,Webpack帮助你补全名称。 fallback 当webpack在 root(默认当前文件夹,配置时要绝对路径) 和 modulesDirectories(默认当前文件夹,相对路径)配置下面找不到相关modules,去哪个文件夹下找modules alias 这个大家应该比较熟悉,requirejs之类的都有,就是别名,帮助你快速指向文件路径,少写不少代码,而且不用关心层级关系,需要注意的是:在scss之类的css预编译中引用要加上~,以便于让loader识别是别名引用路径。
2、module 解析不同文件使用哪些loader,这个比较简单,很多文章都有,就不多说了,注意的是,这里的scss可以换成你自己的预编译器,例如:sass、less、stylus等,或者直接用postcss都行,当然还可以用一种通用方法,后面补上。 `
2. 开发环境配置 config
var webpack = require(‘webpack’); var path = require(‘path’) var merge = require(‘webpack-merge’) var baseConfig = require(‘./webpack.base’) var getEntries = require(‘./getEntries’)
var hotMiddlewareScript = ‘webpack-hot-middleware/client?reload=true’;
var assetsInsert = require(‘./assetsInsert’)
module.exports = merge(baseConfig, { entry: getEntries(hotMiddlewareScript), devtool: ‘#eval-source-map’, output: { filename: ‘./[name].[hash].js’, path: path.resolve(‘./dist’), publicPath:‘./dist’ }, plugins: [ new webpack.DefinePlugin({ ‘process.env’: { NODE_ENV: ‘“development”’ } }), new webpack.optimize.OccurenceOrderPlugin(), new webpack.HotModuleReplacementPlugin(), new webpack.NoErrorsPlugin(), new assetsInsert() ] })
` 说说这个配置中的一些难点:
1、getEntries 是用来配置入口文件,一般很多人是自己手写,或者SPA页面,只有一个入口, 很容易就写出来,但是公司中,很多情况,是需要多入口,也就是多路由的Url,这个时候入口的配置就比较麻烦,我这里是放单独一个文件里面配置,我们公司是靠规定来执行,也就是一个文件夹所有的main.js都认为是入口文件,其他都忽略。 `
function getEntry(hotMiddlewareScript) { var pattern = paths.dev.js + ‘project/**/main.js’; var array = glob.sync(pattern); var newObj = {};
array.map(function(el){
var reg = new RegExp('project/(.*)/main.js','g');
reg.test(el);
if (hotMiddlewareScript) {
newObj[RegExp.$1] = [el, hotMiddlewareScript];
} else {
newObj[RegExp.$1] = el;
}
});
return newObj;
}
2、assetsInsert 是用来做模板替换的,一个小插件把template里面的值替换成打包后的css或者js。
3. 打包环境配置 production
var webpack = require(‘webpack’); var path = require(‘path’) var merge = require(‘webpack-merge’) var baseConfig = require(‘./webpack.base’) var getEntries = require(‘./getEntries’) var ExtractTextPlugin = require(‘extract-text-webpack-plugin’); var assetsInsert = require(‘./assetsInsert’)
var productionConf = merge(baseConfig, { entry: getEntries(), output: { filename: ‘./[name].[hash].js’, path: path.resolve(‘./public/dist’), publicPath: ‘./’ }, plugins: [ new webpack.DefinePlugin({ ‘process.env’: { NODE_ENV: ‘“production”’ } }), new ExtractTextPlugin(‘./[name].[hash].css’, { allChunks: true }), new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } }), new webpack.optimize.OccurenceOrderPlugin(), new assetsInsert() ] })
productionConf.module.loaders = [ { test: /.js$/, loader: ‘babel’, exclude: /node_modules/ }, { test: /.(png|jpe?g|gif|svg|woff2?|eot|ttf|otf)(\?.*)?$/, loader: ‘url?limit=8192&context=client&name=[path][name].[hash:7].[ext]’ }, { test: /.css$/, loader: ExtractTextPlugin.extract(‘style’, ‘css’), }, { test: /.scss$/, loader: ExtractTextPlugin.extract(‘style’, ‘css!sass’) }]
module.exports = productionConf
` 基本跟开发的差不多,差别在于:
1、使用ExtractTextPlugin 来打包css,所以要干掉原来base的loaders,重新写了一个,在最下面。
2、UglifyJsPlugin 给js压缩代码。其他没有什么好解释的了,一样的。 `
4. 构建命令
require(‘shelljs/global’) env.NODE_ENV = ‘production’ var ora = require(‘ora’) var webpack = require(‘webpack’) var webpackConfig = require(‘./webpack.production.config’)
var spinner = ora(‘building for production…’) spinner.start()
var staticPath = __dirname + ‘/../public/dist/’ rm(‘-rf’, staticPath) mkdir(‘-p’, staticPath)
webpack(webpackConfig, function (err, stats) { spinner.stop() if (err) throw err process.stdout.write(stats.toString({ colors: true, modules: false, children: false, chunks: false, chunkModules: false }) + ‘\n’) })
` 写一个build.js,然后在package.json里面添加 script 参数
“build”: “node build.js”//这里记得写自己build.js路径 `
5. 甜点(马卡龙) 有点腻
上面的配置是可以更改的,例如你在loaders 里面加上
{
test: /.vue$/,
loader: ‘vue’
}
`
就可以变成支持.vue文件的vuejs打包构建,同理,修改下支持jsx,和添加一些reactjs的module,就可以用来跑Reactjs的东西。
还有可以随意更改Css预编译器的类型,用你自己喜欢就行,或者跟我们前面提到的方法,把所有类型都配置上, ` var path = require(‘path’) var config = require(‘../config’) var ExtractTextPlugin = require(‘extract-text-webpack-plugin’)
exports.assetsPath = function (_path) { return path.posix.join(config.build.assetsSubDirectory, _path) }
exports.cssLoaders = function (options) { options = options || {} // generate loader string to be used with extract text plugin function generateLoaders (loaders) { var sourceLoader = loaders.map(function (loader) { var extraParamChar if (/\?/.test(loader)) { loader = loader.replace(/\?/, ‘-loader?’) extraParamChar = ‘&’ } else { loader = loader + ‘-loader’ extraParamChar = ‘?’ } return loader + (options.sourceMap ? extraParamChar + ‘sourceMap’ : “) }).join(‘!’)
if (options.extract) {
return ExtractTextPlugin.extract('vue-style-loader', sourceLoader)
} else {
return ['vue-style-loader', sourceLoader].join('!')
}
}
// http://vuejs.github.io/vue-loader/configurations/extract-css.html return { css: generateLoaders([‘css’]), postcss: generateLoaders([‘css’]), less: generateLoaders([‘css’, ‘less’]), sass: generateLoaders([‘css’, ‘sass?indentedSyntax’]), scss: generateLoaders([‘css’, ‘sass’]), stylus: generateLoaders([‘css’, ‘stylus’]), styl: generateLoaders([‘css’, ‘stylus’]) } }
// Generate loaders for standalone style files (outside of .vue) exports.styleLoaders = function (options) { var output = [] var loaders = exports.cssLoaders(options) for (var extension in loaders) { var loader = loaders[extension] output.push({ test: new RegExp(’\.’ + extension + ‘$’), loader: loader }) } return output }
这就是把所有的css预编译的都加到配置里面了。
Network
Cloud
云计算,是用互联网来接入存储或者运行在远程服务器端的应用,数据,服务。所以云计算行业包括了使用基于互联网的方法来计算,存储和开发的公司,或说是在互联网上提供其服务的公司。
IaaS, PasS, SaaS
云计算可以分为不同层次:基础设施-平台-软件,即 Infrastructure as a Service, Platform as a Service, Software as a Service。
IaaS: Infrastructure-as-a-Service 基础设施即服务
提供了远程服务器、存储等网络硬件,使企业在运行应用时不再需要购买服务器等硬件,节省了维护成本和办公场地。实际提供 IaaS 的公司,如 Amozon,MicroSoft,VMWare,Rackspace,RedHat,不仅提供服务器,还会单独提供计算能力。
Paas: Platform-as-a-Service 平台即服务
PaaS,也可以叫中间件,使企业的所有开发都能够在这里进行,以节省时间和资源。PaaS 提供了各种开发和分发应用的解决方案,如虚拟服务器和操作系统。如网页应用管理、应用设计、应用虚拟主机、存储、安全、应用开发协作工具。
大型 PaaS 提供商有: Google App Engine, Microsoft Azure, Force.com, Heroku, Engine Yard; 以及新兴的 AppFog, Mendix, Standing Cloud.
SaaS: Software-as-a-Service 软件即服务
通过远程服务器来运行的应用,就属于 SaaS,也是消费者每天接触到的一层云服务。这类服务常由网页接入,商用+家用的如 Netflx, MOG, Google Apps, Box.net, Dropbox, iCloud。商用 SaaS 应用如 GoToMeeting (Citrix), WebEx (Cisco), CRM (Salesforce), ADP, SuccessFactors (Workday)。
SaaS 之前还有 Application Service Provider 的概念。SaaS 并不一定是B/S架构,重要的是让用户依赖于你在网络上提供的服务,而运营的重点也是如何持续稳定地提供服务。
除了业务服务本身外,SaaS 还需要保障几个服务:
- 服务发现,寻找可用业务服务
- 模块更新,保证客户端模块更新
- 业务服务容器,提供业务服务
- 离线更新,保障分布式系统的数据安全 (??)
云计算并不是把局域网的东西放到广域网上。简单把东西放到网上,进行多用户租赁,只是最初级的SaaS。
SaaS 架构需要的支撑有:分布式计算,分布式数据存储和访问,分布式部署与运维。
- 分布式计算模型是基础的模型。Hadoop 也是一种不太复杂的分布式计算模型。
Proxy
正向代理与反向代理区别
正向代理是位于客户端和原始服务器(origin server)之间的服务器。客户端向原始服务器请求内容时,向代理发送一个指定了目标的请求,由代理向原始服务器转发请求,然后将获得的内容返回客户端。客户端需要对发出的请求进行处理,使代理服务器了解请求的目标。
反向代理中,代理过程完全由客户端访问的服务器完成,代理过程对客户端不可见。客户端发出普通请求,反向代理服务器判断应向哪里的原始服务器转发请求,并将返回的内容作为响应转发回客户端。
正向代理中,客户端通过代理服务器隐藏了自身,如果需要安全性,可以在客户端连接代理服务器时进行鉴权。
用途
- 正向代理:向在防火墙内的局域网客户端提供外网的访问;使用缓冲可以减少对外网的网络访问。
- 反向代理:向外网提供防火墙内的服务器访问;为后端多服务器提供负载平衡;为后端低速服务器提供缓冲;在一个URL空间下提供多服务器多页面的内容。
Norm
Code Review
为什么做 Code Review
做 Code review 是为了改进代码质量. 通过对代码, 测试过程和注释进行检查, 来确认方案设计和代码实现.
Code review 的主要目的:
- 保证代码质量
- 程序逻辑
- 需求和设计的实现
- 查找和排除系统缺陷
- 可读性
可维护性
保证项目组人员的良好沟通
项目或产品的代码更容易维护
- 提高参与者开发水平
- 传递作者的意图和想法,使其他人可以更加熟悉代码,利于维护代码和改进代码
- 评审过程使大家更多的理解系统,重构思路,
- 确定作者的设计和实现清晰,简单
- 帮助初级开发人员学习高级开发人员的经验,达到知识共享, 相互学习,提高开发者水平
次要目的: - 查找程序的bug - 保证代码风格和编码标准 - 编码风格 - 避免开发人员犯一些很常见,很普通的错误 - 在项目早期就能够发现代码中的BUG
消除 Bug 主要靠测试. 由单元测试,功能测试,性能测试,回归测试来消除Bug。 单元测试为主。单元测试最接近bug,bug没有扩散的地方。 在 code review 之前,要有单元测试和单元测试报告,
代码风格和编码标准是确定的, 是靠个人可以做到的, 所以在代码提交时就应该是符合规范的。 代码规范,有作者自己和一些检查代码规范的工具。
规范性 - 注释格式 - 命名规范
- 异常处理
- 日志处理
代码组织结构
业务实现
在开发过程中, 对源代码进行系统检查. 查找代码缺陷, 检查功能实现, 编码合理性, 性能优化.
整理思路,流程的实现方法, 代码的组织方法(不是风格)
CR 的过程
完整的 Code Review 包括三个阶段: 准备, 执行, 追踪
- 准备
- 代码
控制所要 review 的代码数量. 如果代码量比较大, 可对代码按功能模块进行分解, 确定(各部分的)关键代码, 对关键代码进行 review - review 的重点
- 规范
review 的规范用于交流时的统一, 有标准可遁, 提高 review 沟通的效率效果 - 确定参与者
把 review 的代码, 重点, 规范和流程发给所有参与者 - 执行方式 git 式, meeting 式, 1对1 / 多对1 式
- 代码
- implement 执行
- 准确记录
对于CR过程发现的问题,我们必须清晰准确的记录,可以使用问题点记录单,明确记录的项目和内容。 - 讲解与提问 CR过程中,要采用代码作者讲解和审查者提问方式。审查者不能只在发现问题时提问,同时也要根据本次审查的内容要求代码作者对某个特定问题的讲解。
- 逐项审查
对事前确定的审查内容,要逐项审查,不能因为时间不足等因素一扫而过。
- 准确记录
- 追踪
- 确认问题信息
- 问题的影响范围, 解决的难易程度
- 解决问题的时限
- 由谁来解决, 由谁来确认
- 解决过程记录
- 问题的原因
- 解决对策
- 修改的内容
- 确认结果
按时限对修改结果进行全面确认
- 确认问题信息
说明
- 经常做 Code Review, 保持较短的 review 间隔
- 一个人写代码的时间长了, 会在代码中加入越来越多的个性化的东西 :p
- 一次 review 的代码越多, 要修改重构的也越多, 会在讨论中产生太多口水
- 问题解决得越靠前, 总体上所要做的修改就越少
Code Review 要快速, 不要拘泥形式
- 严格按形式进行 Code Review 的问题:
- 只有在 checklist 上的项目才会被 review
- reviewer 在形式上花的精力多了, 在实际思考代码上的精力和耐心就会少
可以花5分钟, 给人讲一下你的代码, 然后花5分钟, 听听他对你代码的意见和想法
觉得有用处的地方, 再做成材料
要能放松, 再放松, 把真正想说的说出来
- 严格按形式进行 Code Review 的问题:
2.0. code review 的过程是讨论问题, 解决问题
记住Review只不过是一种形式,而只有在相互信任中通过相互的讨论得到了有意义和有建设性的建议和意见,那才是最实在的。不然,作者和评审者的关系就会变成小偷和警察的关系。
每人都需要让不同的人 review 你的代码
- 每次可以找两三个人来做 review, 这样3,4个人可以围在一起讨论, 每个人都能充分参与, 也不会因为意见太多降低执行的效率
- 不同的人有不同的思考方式, 对同一功能和设计的实现有不同的想法, 可以从不同方向更全面地来过你的代码
- 参与的人可以更好地理解你的代码, 团队中每个人的思考可以更同步, 在日后代码维护时相互帮助也更容易
Enjoy 学会享受
傲慢是程序员三大美德之一. 当看别人的代码时, 或者代码被人修改时, 自负的情绪会让人不容易接受不同的想法. Code review 需要一种积极的态度提意见和接受意见, 不要抨击别人的代码, 不要质疑别人的能力, 给同事的建议和同事给的建议, 都是为了大家能做得更好.
Code review 的核心是讨论问题, 解决问题. 团队里人人都喜欢讨论问题, 解决问题, 那每个人都能写高质量的代码, 大家相互学习, 相互帮助, 每个人都能更好地进化, 团队能快速适应变化.
关键是, 开心的才是更好的 :-)
准备工作
- 参与人对自己在 code review 要做什么, 要学什么有基本的了解
- 代码能正确运行/编译, 功能基本正确
- 代码能通过单元测试和测试用例
- 做 review 的人对代码有基本的了解, 代码的功能是什么, 涉及什么系统的什么方面, 对性能,稳定等方面有什么特别的需求, 好有针对性地检查代码
Code review 前代码的语法和功能问题应该已经解决, review 的重点在代码质量上.
GIT 提交
- 提交日志有明确的含义,明确此次提交的内容或目的 像“修改了某文件”,“更行了某文件”提供的信息太少。
写日志时考虑log的目的是在查找修改bug,版本回滚,代码评审等过程中提供有效的信息。
- 提交的大小 提交的代码太多,不便于代码评审和版本控制. 提交太多太小的修改,会影响版本和日志的可读性。 可以参考的做法是,对一个模块完成修改,昨晚单元测试后,压缩commit之后提交。
避免的做法:提交未测试的代码,出现问题后提交很多小的修改。
Review 项目
基本方面 - 代码一致性 - 代码安全问题 - 代码冗余 - 设计的正确性 - 性能与功能需求
代码中使用的格式、符号、结构等风格是否保持一致
- 完整性 completeness
- 完全实现设计文档中的功能需求
- 创建并初始化了所需要的数据库
- 去除了未定义或未使用的变量, 常量, 类
- 一致性 consistency
- 代码逻辑与文档中逻辑是否一致
- 代码中的算法符合文档中的数学模型
- 正确性 correctness
- 注释准确完整
- 函数调用参数传递正确
- 变量定义和使用方式正确
- 代码组织方式符合制定的标准
- 清晰性 clearness
- 所需的数据字典, 接口说明, 描述如何访问常量, 变量, 功能
- 配置, 定义文件是否易于理解, 修改
- 功能模块功能的入口和出口明确, 唯一
- 函数的出口唯一 (异常处理除外)
- 每个功能都是一个可辩识的代码块
- 可预测性 predictability
- 代码语言中常见陷阱已检测并排除
- 是否有依赖于开发工具环境语言缺省提供的功能
- 代码中没有死循环, 无穷递归
- 健壮性 robustness
- 采取措施避免运行时错误: 数组边界溢出, 被零除, 值越界, 堆栈溢出, etc
- 可追溯性 traceability
- 每个程序有唯一标识
- 代码和开发文档之间有交叉引用(框架)可以相互对应
- 代码版本管理中对代码的修改和原因都有易于理解的记录
- 可读性 readability
- 注释清晰描述了每个子程序
- 复杂代码有必要的使用理由, 注释清楚明确
- 使用一些统一的格式化技巧(如缩进、空白等)用来增强代码的清晰度
- 变量, 函数的命名清楚反映含义, 便于记忆, 书写
- 变量都有合理合法的取值范围
- 可验证性 verifiability
- 代码中功能的实现方式便于测试
Review 步骤
- 作者向参与review的人按用例或功能依次讲解自己的代码功能, 相关逻辑
- reviewer 提出疑问, 组织者对存在的问题做记录
- reviewer 自己对代码进行审核, 并把问题和修改建议记录在表中
- 组织者把各reviewer的代码问题记录表汇总成”review报告”, 记录发现的问题和修改建议,然后把”review报告”发送给相关人员
- 作者根据”review报告”的修改意见修改代码, 有不清楚的地方和reviewer进行交流
- 作者在过完 issue 之后作出反馈
- 组织者把 Code Review 中发现的有价值的问题更新到 “code review 规范” 文档中
- 对值得提醒和注意的问题可 email 给所有技术人员
所需文档
- code review 规范
- issue 记录表
- issue 汇总表
- issue 解决表
- meeting 记录
- output to protocol documents
ref
相关资料 5个开源的代码审查工具
资料来源
SQL
Declare a Variable in MySQL
(ref)
There are mainly three types of variables in MySQL:
User-defined variables (prefixed with @):
You can access any user-defined variable without declaring it or initializing it. If you refer to a variable that has not been initialized, it has a value of NULL and a type of string.
SELECT @var_any_var_name You can initialize a variable using SET or SELECT statement:
SET @start = 1, @finish = 10;
or
SELECT @start := 1, @finish := 10;
SELECT * FROM places WHERE place BETWEEN @start AND @finish; User variables can be assigned a value from a limited set of data types: integer, decimal, floating-point, binary or nonbinary string, or NULL value.
User-defined variables are session-specific. That is, a user variable defined by one client cannot be seen or used by other clients.
They can be used in SELECT queries using Advanced MySQL user variable techniques. Local Variables (no prefix) :
Local variables needs to be declared using DECLARE before accessing it.
They can be used as local variables and the input parameters inside a stored procedure:
DELIMITER //
CREATE PROCEDURE sp_test(var1 INT)
BEGIN
DECLARE start INT unsigned DEFAULT 1;
DECLARE finish INT unsigned DEFAULT 10;
SELECT var1, start, finish;
SELECT * FROM places WHERE place BETWEEN start AND finish;
END; //
DELIMITER ;
CALL sp_test(5);
If the DEFAULT clause is missing, the initial value is NULL.
The scope of a local variable is the BEGIN … END block within which it is declared. Server System Variables (prefixed with @@):
The MySQL server maintains many system variables configured to a default value. They can be of type GLOBAL, SESSION or BOTH.
Global variables affect the overall operation of the server whereas session variables affect its operation for individual client connections.
To see the current values used by a running server, use the SHOW VARIABLES statement or SELECT @@var_name.
SHOW VARIABLES LIKE ‘%wait_timeout%‘;
SELECT @@sort_buffer_size;
They can be set at server startup using options on the command line or in an option file. Most of them can be changed dynamically while the server is running using SET GLOBAL or SET SESSION:
-- Syntax to Set value to a Global variable:
SET GLOBAL sort_buffer_size=1000000;
SET @@global.sort_buffer_size=1000000;
-- Syntax to Set value to a Session variable:
SET sort_buffer_size=1000000;
SET SESSION sort_buffer_size=1000000;
SET @@sort_buffer_size=1000000;
SET @@local.sort_buffer_size=10000;
Tools
Makefile
Make Basic
make is designed to manage the compiling process based on dependencies. The other functions of make is designed to make it easier to write Makefile.
When we deal with a complex project, there might be lots of source files, header files, lib files, target files. The files have a certain dependency relationship.
$ gcc -c -o test.o test.c
$ gcc -o helloworld test.o
The dependency line is: test.c
-> test.o
-> helloworld
.
When dealing with many files, the compiler is called many times. We build the files on which is depended, then build target files on top of that.
And make is a tool to automatically record and process the dependency. The dependency relationship is written in the Makefile file, and make will compile the files w.r.t. the dependency described in Makefile.
Dependency
Here is a basic Makeifle
# target binary file: helloworld
helloworld: test.o
echo "good"
gcc -o helloworld test.o
test.o: test.c
gcc -c -o test.o test.c
- # starts a comment line
- a target depends on its prerequisite. Multiple files are separated by spaces.
- The lines indented by Tab are operations to implement a dependency relationship. These operations are normal Shell commands.
make is a recursive process.
- If the current dependency has not prerequisite, then execute operations directly.
- If the current dependency has prerequisite, and the required files exist and have not been changed since the last make (judged by write-timestamp), then execute operations directly.
- If the required files of the current dependency do not exist, or have been changed, then use the required files as target files, and search their dependency, create target files.
Macro
Macro in make is text-type variable.
CC = gcc
helloworld: test.o
echo "good"
$(CC) -o helloworld test.o
test.o: test.c
$(CC) -c -o test.o test.c
Inner Macro
$@
is target filename of the current dependency.$^
is required filenames of the current dependency.$*
target filename without suffix of the current dependency$*
the changed files in the current dependency$$
character $$(@D)
directory of file path$(@F)
filename of file path
CC = gcc
helloworld: test.o
echo $@
$(CC) -o $@ $^
test.o: test.c
$(CC) -c -o $@ $^
Suffix Dependency
CC = gcc
.SUFFIXES: .c .o
.c.o:
$(CC) -c -o $@ $^
#--------------------------
helloworld: test.o
echo $@
$(CC) -o $@ $^
test.o: test.c
.c.o
means .c
is prerequisite, and .o
is target.
make will find test.o and test.c has dependency, and will execute operation for this dependency.
Suffix dependency is very handy is big projects.
Misc
Special dependency in makefile.
all: if make was followed by no filename, “all” will be chosen.
clean: it is used to clean legacy files.
CC = gcc
.SUFFIXES: .c .o
.c.o:
$(CC) -c -o $@ $^
#--------------------------
all: helloworld
@echo "ALL"
helloworld: test.o
@echo $@
$(CC) -o $@ $^
test.o: test.c
clean:
-rm helloworld *.o
’@’ will hide the following command itself. ‘-’ will ignore the stderr of the following command.
Python
Class 自带函数
class Foor(object):
def __init__(self, *args, **kwargs):
print('init')
super(Bar, self).__init__(*args, **kwargs)
def __new__(cls, *args, **kwargs):
print('new')
return super(Bar, cls).__new__(cls, *args, **kwargs)
def __call__(self, *args, **kwargs):
print('call')
foo = Bar()
foo()
# Result:
new
init
call
__init__(self, *args, **kwargs)
在对象创建完成后调用,对当前对象实例进行初始化,无返回值。__new__(cls, *args, **kwargs)
创建对象时调用,返回当前对象的一个实例。__call__(self, *args, **kwargs)
重载括号运算符。类实现了call方法,则其对象可当作函数使用。
用自带函数实现单例
class MySingleton(object):
def __new__(cls, *args, **kwargs):
if not '_instance' in vars(cls):
cls._instance = super(MySingleton, cls).__new__(cls, *args, **kwargs)
return cls._instance
review 记录:作者记录,表格记录,checkout -b review, checkout -b review_by_Foo, git pull && checkout -b review_by_Foo
http://blog.csdn.net/haoel/article/details/4469526
http://blog.csdn.net/haoel/article/details/4469462
http://www.cnblogs.com/lhb25/p/15-best-code-review-tools-for-developers.html
Code Review中的几个提示
Code Review应该是软件工程最最有价值的一个活动,之前,本站发表过《简单实用的Code Review工具》,那些工具主要是用来帮助更有效地进行这个活动,这里的这篇文章,我们主要想和大家分享一下Code Review代码审查的一些心得。
首先,我们先来看看Code Reivew的用处:
Code reviews 中,可以通过大家的建议增进代码的质量。 Code reviews 是一个传递知识的手段,可以让其它并不熟悉代码的人知道作者的意图和想法,从而可以在以后轻松维护代码。 Code reviews 也鼓励程序员们相互学习对方的长处和优点。 Code reviews 也可以被用来确认自己的设计和实现是一个清楚和简单的。 你也许注意到了在上面的Code Reivew中的诸多用处中,我们没有提到可以帮助找到程序的bug和保证代码风格和编码标准。这是因为我们认为:
Code reviews 不应该承担发现代码错误的职责。Code Review主要是审核代码的质量,如可读性,可维护性,以及程序的逻辑和对需求和设计的实现。代码中的bug和错误应该由单元测试,功能测试,性能测试,回归测试来保证的(其中主要是单元测试,因为那是最接近Bug,也是Bug没有扩散的地方)
所以,在今天,请不要把上面的那两件事分散了Code Review的注意力,取而代之的是,对于Bug,程序的作者要在Review前提交自己的单元测试报告(如:XUnit的测试结果),对于代码规范,这是程序作者自己需要保证的,而且,有一些工具是可以帮你来检查代码规范的。
不是说不能在Code Review中报告一个程序的bug或是一个代码规范的问题。只是说那并不是Code Review的意图。
1.- 经常进行Code Review 以前经历过几个相当痛苦的Code Review,那几次Code Review都是在程序完成的时候进行的,当你面对那近万行的代码,以前N我掺和在一起的功能,你会发现,整个Code Review变得非常地艰难,用不了一会儿,你就会发现大家都在拼命地打着哈欠,但还是要坚持,有时候,这样的Review会持续3个小时以上,相当的夸张。而且,会议上会出现相当多的问题和争论,因为,这就好像,人家都把整个房子盖好了,大家Review时这挑一点那挑一点,有时候触动地基或是承重墙体,需要大动手术,让人返工,这当然会让盖房的人一下就跳起来极力地维护自己的代码,最后还伤了团队成员的感情。
所以,千万不要等大厦都盖好了再去Reivew,而且当有了地基,有了框架,有了房顶,有了门窗,有了装修,的各个时候循序渐进地进行Review,这样反而会更有效率,也更有帮助。
下面是一些观点,千万要铭记:
要Review的代码越多,那么要重构,重写的代码就会越多。而越不被程序作者接受的建议也会越多,唾沫口水战也会越多。 程序员代码写得时候越长,程序员就会在代码中加入越来越多的个人的东西。 程序员最大的问题就是“自负”,无论什么时候,什么情况下,有太多的机会会让这种“自负”澎涨开来,并开始影响团队影响整个项目,以至于听不见别人的建议,从而让Code Review变成了口水战。 越接近软件发布的最终期限,代码也就不能改得太多。 我个人的习惯,并且也是对团队成员的要求是——先Review设计实现思路,然后Review设计模式,接着Review成形的骨干代码,最后Review完成的代码,如果程序复杂的话,需要拆成几个单元或模块分别Review。当然,最佳的practice是,每次Review的代码应该在1000行以内,时间不能超过一部电影的时间——1.5小时(因为据说那个一个正常人的膀胱可以容纳尿液的最长限度)
当然,在敏捷开发中,他们不需要Code Reivew,其实,敏捷开发中使用更为极端的“结对编程”(Pair-Programming)的方法 —— 一种时时刻刻都在进行Code Review的方法,个人感觉在实际过程中,这种方法有点过了。另外,大家可以看看本站的另一篇文章《结对编程的利与弊》来了解一下这种方法的问题。
3.- 尽可能的让不同的人Reivew你的代码 这是一个好主意,如果可能的话,不要总是只找一个人来Review你的代码,不同的人有不同的思考方式,有不同的见解,所以,不同的人可以全面的从各个方面评论你的代码,有的从实现的角度,有的从需求的角度,有的从用户使用的角度,有的从算法的角度,有的从性能效率的角度,有的从易读的角度,有的从扩展性的角度……,啊,好多啊,还让不让人活了。不管怎么说,多找一些不同的人会对你很有好处。当然,不要太多了,人多嘴杂反而适得其反,基本上来说,不要超过3个人,这是因为,这是一个可以围在一起讨论的最大人员尺寸。
从不同的方向评审代码总是好的。 会有更多的人帮你在日后维护你的代码。 这也是一个增加团队凝聚力的方法。
Code Review中文应该译作“代码审查”或是“代码评审”,这是一个流程,当开发人员写好代码后,需要让别人来review一下他的代码,这是一种有效发现BUG的方法。由此,我们可以审查代码的风格、逻辑、思路……,找出问题,以及改进代码。因为这是代码刚刚出炉的时候,所以,这也是代码重构,代码调整,代码修改的最佳时候。所以,Code Review是编码实现中最最重要的一个环节。
长时间以来,Code Review需要有一些有效的工具来支持,这样我们就可以更容易,更有效率地来进行代码审查工作。下面是5个开源的代码审查工具,他们可以帮助你更容易地进行这项活动。
- Review board: Review board 是一个 基于web 的工具,主要设计给 django 和python的用户。 Review board 可以帮助我们追踪待决代码的改动,并可以让Code-Review更为容易和简练。尽管Review board 最初被设计在VMware项目中使用,但现在其足够地通用。当前,其支持这些代码版本管理软件: SVN, CVS, Perforce, Git, Bazaar, 和Mercurial.
Yahoo 是review-board的其中一个用户。
“Review board 已经改变了代码评审的方式,其可以强迫高质量的代码标准和风格,并可以成为程序员编程的指导者。每一次,当你访问search.yahoo.com 时,其代码都是使用 Review board工具Review过的。 We’re great fans of your work!” – Yahoo! Web Search
Detailed review requests Powerful diff viewer
- Codestriker: Codestriker 也是一个基于Web的应用,其主要使用 GCI-Perl 脚本支持在线的代码审查。Codestriker 可以集成于CVS, Subversion, ClearCase, Perforce 和Visual SourceSafe。并有一些插件可以提供支持其它的源码管理工具。
David Sitsky 是 Codestriker 的作者,并也是最活跃的开发人员之一。 Jason Remillard 是另一个活路的开发者,并给这个项目提供了最深远最有意义的贡献。大量的程序员贡献他们的代码给 Codestriker 项目,导致了这个项目空前的繁荣。
http://codestriker.sourceforge.net/viewtopicdetail.png
- Groogle: Groogle 是一个基于WEB的代码评审工具。 Groogle 支持和 Subversion 集成。它主要提供如下的功能:
各式各样语言的语法高亮。 支持整个版本树的比较。 支持当个文件不同版本的diff功能,并有一个图形的版本树。 邮件通知所有的Reivew的人当前的状态。 认证机制。 Screenshot
Rietveld: Rietveld 由Guido van Rossum 开发(他是Python的创造者,现在是Google的员工),这个工具是基于Mondrian 工具,作者一开始是为了Google 开发的,并且,它在很多方面和Review board 很像。它也是一个基于Web的应用,并可以Google App Engine 当主机。它使用了目前最流行的Web开发框架 django 并支持 Subversion 。当前,任何一个使用 Google Code 的项目都可以使用 Rietveld 并且使用 python Subversion 服务器。当然,它同样支持其它的Subversion服务器。
JCR JCR 或者叫做 JCodeReview 也是一个基于WEB界面的最初设计给Reivew Java 语言的一个工具。当然,现在,它可以被用于其它的非Java的代码。
JCR 主要想协助:
审查者。所有的代码更改都会被高亮,以及大多数语言的语法高亮。Code extracts 可以显示代码评审意见。如果你正在Review Java的代码,你可以点击代码中的类名来查看相关的类的声明。 项目所有者。可以 轻松创建并配置需要Review的项目,并不需要集成任何的软件配置管理系统(SCM)。 流程信仰者。 所有的评语都会被记录在数据库中,并且会有状态报告,以及各种各样的统计。 架构师和开发者。 这个系统也可以让我们查看属于单个文件的评语,这样有利于我们重构代码。 JCR 主要面对的是大型的项目,或是非常正式的代码评审,从这方面看来,他并不像上面的那些工具。
Screenshot
Jupiter:最后我们要提一下Jupiter,这是另一个代码review的工具你可以去考虑使用的,它是一个Eclipse IDE 的插件。
new T1 算法 Schedule 48
所有时间估计实施时长乘系数 0.5~3
程序
apps 23.5 + 3*?
- bills 4.5
- list 1⁄4
- get 1⁄4
- 入库 1
- 出库 1
- 指定单据入库退货manual 1
- 指定单据出库退货manual 1
stocks 11 + ? + ? + ?
- app.stocks 3 + ?
- 入库 1⁄4
- 出库 1⁄4
- list 1⁄8
- get 1⁄8
- 指定单据入库退货manual 1
- 指定单据出库退货manual 5⁄4
- 修改 ??
- api.settlement 3 + ?
- 入库 1⁄4
- 出库 1⁄4
- list 1⁄8
- get 1⁄8
- 指定单据入库退货manual 1
- 指定单据出库退货manual 5⁄4
- 修改 ??
- 关联表 1
- 出库关联 1⁄8
- 关联修改 1⁄8
- 关联调用 3⁄4
- app.inventory 4 + ?
- 入库 1⁄2
- 出库 1⁄2
- list 1⁄8
- get 1⁄8
- 指定单据入库退货manual 5⁄4
- 指定单据出库退货manual 6⁄4
- 修改 ??
- app.stocks 3 + ?
queues 8
- 入队
- 处理
- 完成
- 单记录单步的操作
- 单记录顺序的操作
- 插入新记录的处理
- 多记录
- 清除
models 8
- settlement 修改 1
2016-12-25 数据库单元用例时发现的settlement的问题 - inventory 全部 3
- queues 全部 4
单元用例 12
- bills 4
- stocks 4
- stocks 1+
- settlement 1+
- inventory 1+
- queues 4
集成用例 4
- 出入库 4
- 指定单据退货manual
解决方案 8
超过8小时就不做,先确定问题
指定单据退货fifo 指定单据退货avg 不指定单据退货manual 不指定单据退货fifo 不指定单据退货avg 修改单据