优大网

3 / 15

优秀程序员眼中的整洁代码

有多少程序员,就有多少定义。所以我只询问了一些非常知名且经验丰富的程序员。

  Bjarne Stroustrup,C++语言发明者,C++ Programming Language(中译版《C++程序设计语言》)一书作者。

我喜欢优雅和高效的代码。代码逻辑应当直截了当,叫缺陷难以隐藏;尽量减少依赖关系,使之便于维护;依据某种分层战略完善错误处理代码;性能调至最优,省得引诱别人做没规矩的优化,搞出一堆混乱来。整洁的代码只做好一件事。

Bjarne用了“优雅”一词。说得好!我MacBook上的词典提供了如下定义:外表或举止上令人愉悦的优美和雅观;令人愉悦的精致和简单。注意对“愉悦”一词的强调。Bjarne显然认为整洁的代码读起来令人愉悦。读这种代码,就像见到手工精美的音乐盒或者设计精良的汽车一般,让你会心一笑。

Bjarne也提到效率——而且两次提及。这话出自C++发明者之口,或许并不出奇;不过我认为并非是在单纯追求速度。被浪费掉的运算周期并不雅观,并不令人愉悦。留意Bjarne怎么描述那种不雅观的结果。他用了“引诱”这个词。诚哉斯言。糟糕的代码引发混乱!别人修改糟糕的代码时,往往会越改越烂。

务实的Dave Thomas和Andy Hunt从另一角度阐述了这种情况。他们提到破窗理论4。窗户破损了的建筑让人觉得似乎无人照管。于是别人也再不关心。他们放任窗户继续破损。最终自己也参加破坏活动,在外墙上涂鸦,任垃圾堆积。一扇破损的窗户开辟了大厦走向倾颓的道路。

Bjarne也提到完善错误处理代码。往深处说就是在细节上花心思。敷衍了事的错误处理代码只是程序员忽视细节的一种表现。此外还有内存泄漏,还有竞态条件代码。还有前后不一致的命名方式。结果就是凸现出整洁代码对细节的重视。

Bjarne以“整洁的代码只做好一件事”结束论断。毋庸置疑,软件设计的许多原则最终都会归结为这句警语。有那么多人发表过类似的言论。糟糕的代码想做太多事,它意图混乱、目的含混。整洁的代码力求集中。每个函数、每个类和每个模块都全神贯注于一事,完全不受四周细节的干扰和污染。

  Grady Booch,Object Oriented Analysis and Design with Applications(中译版《面向对象分析与设计》)一书作者。

整洁的代码简单直接。整洁的代码如同优美的散文。整洁的代码从不隐藏设计者的意图,充满了干净利落的抽象和直截了当的控制语句。

Grady的观点与Bjarne的观点有类似之处,但他从可读性的角度来定义。我特别喜欢“整洁的代码如同优美的散文”这种看法。想想你读过的某本好书。回忆一下,那些文字是如何在脑中形成影像!就像是看了场电影,对吧?还不止!你还看到那些人物,听到那些声音,体验到那些喜怒哀乐。

阅读整洁的代码和阅读Lord of the Rings(中译版《指环王》)自然不同。不过,仍有可类比之处。如同一本好的小说般,整洁的代码应当明确地展现出要解决问题的张力。它应当将这种张力推至高潮,以某种显而易见的方案解决问题和张力,使读者发出“啊哈!本当如此!”的感叹。

窃以为Grady所谓“干净利落的抽象”(crisp abstraction),乃是绝妙的矛盾修辞法。毕竟crisp几乎就是“具体”(concrete)的同义词。我MacBook上的词典这样定义crisp一词:果断决绝,就事论事,没有犹豫或不必要的细节。尽管有两种不同的定义,该词还是承载了有力的信息。代码应当讲述事实,不引人猜测。它只该包含必需之物。读者应当感受到我们的果断决绝。

  “老大”Dave Thomas,OTI公司创始人,Eclipse战略教父

整洁的代码应可由作者之外的开发者阅读和增补。它应当有单元测试和验收测试。它使用有意义的命名。它只提供一种而非多种做一件事的途径。它只有尽量少的依赖关系,而且要明确地定义和提供清晰、尽量少的API。代码应通过其字面表达含义,因为不同的语言导致并非所有必需信息均可通过代码自身清晰表达。

Dave老大在可读性上和Grady持相同观点,但有一个重要的不同之处。Dave断言,整洁的代码便于其他人加以增补。这看似显而易见,但亦不可过分强调。毕竟易读的代码和易修改的代码之间还是有区别的。

Dave将整洁系于测试之上!要在十年之前,这会让人大跌眼镜。但测试驱动开发(Test Driven Development)已在行业中造成了深远影响,成为基础规程之一。Dave说得对。没有测试的代码不干净。不管它有多优雅,不管有多可读、多易理解,微乎测试,其不洁亦可知也。

Dave两次提及“尽量少”。显然,他推崇小块的代码。实际上,从有软件起人们就在反复强调这一点。越小越好。

Dave也提到,代码应在字面上表达其含义。这一观点源自Knuth的“字面编程”(literate programming)5。结论就是应当用人类可读的方式来写代码。

  Michael Feathers,Working Effectively with Legacy Code(中译版《修改代码的艺术》)一书作者。

我可以列出我留意到的整洁代码的所有特点,但其中有一条是根本性的。整洁的代码总是看起来像是某位特别在意它的人写的。几乎没有改进的余地。代码作者什么都想到了,如果你企图改进它,总会回到原点,赞叹某人留给你的代码——全心投入的某人留下的代码。

一言以蔽之:在意。这就是本书的题旨所在。或许该加个副标题,如何在意代码

Michael一针见血。整洁代码就是作者着力照料的代码。有人曾花时间让它保持简单有序。他们适当地关注到了细节。他们在意过。

  Ron Jeffries,Extreme Programming Installed(中译版《极限编程实施》)以及Extreme Programming Adventures in C#(中译版《C#极限编程探险》)作者。

Ron初入行就在战略空军司令部(Strategic Air Command)编写Fortran程序,此后几乎在每种机器上编写过每种语言的代码。他的言论值得咀嚼。

近年来,我开始研究贝克的简单代码规则,差不多也都琢磨透了。简单代码,依其重要顺序:

  • 能通过所有测试;
  • 没有重复代码;
  • 体现系统中的全部设计理念;
  • 包括尽量少的实体,比如类、方法、函数等。

在以上诸项中,我最在意代码重复。如果同一段代码反复出现,就表示某种想法未在代码中得到良好的体现。我尽力去找出到底那是什么,然后再尽力更清晰地表达出来。

在我看来,有意义的命名是体现表达力的一种方式,我往往会修改好几次才会定下名字来。借助Eclipse这样的现代编码工具,重命名代价极低,所以我无所顾忌。然而,表达力还不只体现在命名上。我也会检查对象或方法是否想做的事太多。如果对象功能太多,最好是切分为两个或多个对象。如果方法功能太多,我总是使用抽取手段(Extract Method)重构之,从而得到一个能较为清晰地说明自身功能的方法,以及另外数个说明如何实现这些功能的方法。

消除重复和提高表达力让我在整洁代码方面获益良多,只要铭记这两点,改进脏代码时就会大有不同。不过,我时常关注的另一规则就不太好解释了。

这么多年下来,我发现所有程序都由极为相似的元素构成。例如“在集合中查找某物”。不管是雇员记录数据库还是名-值对哈希表,或者某类条目的数组,我们都会发现自己想要从集合中找到某一特定条目。一旦出现这种情况,我通常会把实现手段封装到更抽象的方法或类中。这样做好处多多。

可以先用某种简单的手段,比如哈希表来实现这一功能,由于对搜索功能的引用指向了我那个小小的抽象,就能随需应变,修改实现手段。这样就既能快速前进,又能为未来的修改预留余地。

另外,该集合抽象常常提醒我留意“真正”在发生的事,避免随意实现集合行为,因为我真正需要的不过是某种简单的查找手段。

减少重复代码,提高表达力,提早构建简单抽象。这就是我写整洁代码的方法。

Ron以寥寥数段文字概括了本书的全部内容。不要重复代码,只做一件事,表达力,小规模抽象。该有的都有了。

  Ward Cunningham,Wiki发明者,eXtreme Programming(极限编程)的创始人之一,Smalltalk语言和面向对象的思想领袖。所有在意代码者的教父。

如果每个例程都让你感到深合己意,那就是整洁代码。如果代码让编程语言看起来像是专为解决那个问题而存在,就可以称之为漂亮的代码。

这种说法很Ward。它教你听了之后就点头,然后继续听下去。如此在理,如此浅显,绝不故作高深。你大概以为此言深合己意吧。再走近点看看。

“……深合己意”。你最近一次看到深合己意的模块是什么时候?模块多半都繁复难解吧?难道没有触犯规则吗?你不是也曾挣扎着想抓住些从整个系统中散落而出的线索,编织进你在读的那个模块吗?你最近一次读到某段代码、并且如同对Ward的说法点头一般对这段代码点头,是什么时候的事了?

Ward期望你不会为整洁代码所震惊。你无需花太多力气。那代码就是深合你意。它明确、简单、有力。每个模块都为下一个模块做好准备。每个模块都告诉你下一个模块会是怎样的。整洁的程序好到你根本不会注意到它。设计者把它做得像一切其他设计般简单。

那Ward有关“美”的说法又如何呢?我们都曾面临语言不是为要解决的问题所设计的困境。但Ward的说法又把球踢回我们这边。他说,漂亮的代码让编程语言像是专为解决那个问题而存在!所以,让语言变得简单的责任就在我们身上了!当心,语言是冥顽不化的!是程序员让语言显得简单。

程序员必须知道的10大基础实用算法及其讲解

算法一:快速排序算法

快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要Ο(n log n)次比较。在最坏状况下则需要Ο(n2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他Ο(n log n) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。

快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。

算法步骤:

1 从数列中挑出一个元素,称为 “基准”(pivot),

2 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。

3 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。

Sorting_quicksort_anim

详细介绍:快速排序

算法二:堆排序算法

堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

堆排序的平均时间复杂度为Ο(nlogn) 。

算法步骤:

  1. 创建一个堆H[0..n-1]
  2. 把堆首(最大值)和堆尾互换

3. 把堆的尺寸缩小1,并调用shift_down(0),目的是把新的数组顶端数据调整到相应位置

4. 重复步骤2,直到堆的尺寸为1

Sorting_heapsort_anim

详细介绍:堆排序

算法三:归并排序

归并排序(Merge sort,台湾译作:合并排序)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。

算法步骤:

1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列

2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置

3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置

4. 重复步骤3直到某一指针达到序列尾

5. 将另一序列剩下的所有元素直接复制到合并序列尾

Merge_sort_animation2

详细介绍:归并排序

算法四:二分查找算法

二分查找算法是一种在有序数组中查找某一特定元素的搜索算法。搜素过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜素过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组为空,则代表找不到。这种搜索算法每一次比较都使搜索范围缩小一半。折半搜索每次把搜索区域减少一半,时间复杂度为Ο(logn) 。

详细介绍:二分查找算法

算法五:BFPRT(线性查找算法)

BFPRT算法解决的问题十分经典,即从某n个元素的序列中选出第k大(第k小)的元素,通过巧妙的分析,BFPRT可以保证在最坏情况下仍为线性时间复杂度。该算法的思想与快速排序思想相似,当然,为使得算法在最坏情况下,依然能达到o(n)的时间复杂度,五位算法作者做了精妙的处理。

算法步骤:

1. 将n个元素每5个一组,分成n/5(上界)组。

2. 取出每一组的中位数,任意排序方法,比如插入排序。

3. 递归的调用selection算法查找上一步中所有中位数的中位数,设为x,偶数个中位数的情况下设定为选取中间小的一个。

4. 用x来分割数组,设小于等于x的个数为k,大于x的个数即为n-k。

5. 若i==k,返回x;若i<k,在小于x的元素中递归查找第i小的元素;若i>k,在大于x的元素中递归查找第i-k小的元素。

终止条件:n=1时,返回的即是i小元素。

详细介绍:

寻找最小(最大)的k个数

线性查找相关算法

算法六:DFS(深度优先搜索)

深度优先搜索算法(Depth-First-Search),是搜索算法的一种。它沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点v的所有边都己被探寻过,搜索将回溯到发现节点v的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止。DFS属于盲目搜索。

深度优先搜索是图论中的经典算法,利用深度优先搜索算法可以产生目标图的相应拓扑排序表,利用拓扑排序表可以方便的解决很多相关的图论问题,如最大路径问题等等。一般用堆数据结构来辅助实现DFS算法。

深度优先遍历图算法步骤:

1. 访问顶点v;

2. 依次从v的未被访问的邻接点出发,对图进行深度优先遍历;直至图中和v有路径相通的顶点都被访问;

3. 若此时图中尚有顶点未被访问,则从一个未被访问的顶点出发,重新进行深度优先遍历,直到图中所有顶点均被访问过为止。

上述描述可能比较抽象,举个实例:

DFS 在访问图中某一起始顶点 v 后,由 v 出发,访问它的任一邻接顶点 w1;再从 w1 出发,访问与 w1邻 接但还没有访问过的顶点 w2;然后再从 w2 出发,进行类似的访问,… 如此进行下去,直至到达所有的邻接顶点都被访问过的顶点 u 为止。

接着,退回一步,退到前一次刚访问过的顶点,看是否还有其它没有被访问的邻接顶点。如果有,则访问此顶点,之后再从此顶点出发,进行与前述类似的访问;如果没有,就再退回一步进行搜索。重复上述过程,直到连通图中所有顶点都被访问过为止。

详细介绍:深度优先搜索

 

算法七:BFS(广度优先搜索)

广度优先搜索算法(Breadth-First-Search),是一种图形搜索算法。简单的说,BFS是从根节点开始,沿着树(图)的宽度遍历树(图)的节点。如果所有节点均被访问,则算法中止。BFS同样属于盲目搜索。一般用队列数据结构来辅助实现BFS算法。

算法步骤:

1. 首先将根节点放入队列中。

2. 从队列中取出第一个节点,并检验它是否为目标。

  • 如果找到目标,则结束搜寻并回传结果。
  • 否则将它所有尚未检验过的直接子节点加入队列中。

3. 若队列为空,表示整张图都检查过了——亦即图中没有欲搜寻的目标。结束搜寻并回传“找不到目标”。

4. 重复步骤2。

Animated_BFS

详细介绍:广度优先搜索

算法八:Dijkstra算法

戴克斯特拉算法(Dijkstra’s algorithm)是由荷兰计算机科学家艾兹赫尔·戴克斯特拉提出。迪科斯彻算法使用了广度优先搜索解决非负权有向图的单源最短路径问题,算法最终得到一个最短路径树。该算法常用于路由算法或者作为其他图算法的一个子模块。

该算法的输入包含了一个有权重的有向图 G,以及G中的一个来源顶点 S。我们以 V 表示 G 中所有顶点的集合。每一个图中的边,都是两个顶点所形成的有序元素对。(uv) 表示从顶点 u 到 v 有路径相连。我们以 E 表示G中所有边的集合,而边的权重则由权重函数 wE → [0, ∞] 定义。因此,w(uv) 就是从顶点 u 到顶点 v 的非负权重(weight)。边的权重可以想像成两个顶点之间的距离。任两点间路径的权重,就是该路径上所有边的权重总和。已知有 V 中有顶点 s 及 t,Dijkstra 算法可以找到 s 到 t的最低权重路径(例如,最短路径)。这个算法也可以在一个图中,找到从一个顶点 s 到任何其他顶点的最短路径。对于不含负权的有向图,Dijkstra算法是目前已知的最快的单源最短路径算法。

算法步骤:

1. 初始时令 S={V0},T={其余顶点},T中顶点对应的距离值

若存在<V0,Vi>,d(V0,Vi)为<V0,Vi>弧上的权值

若不存在<V0,Vi>,d(V0,Vi)为∞

2. 从T中选取一个其距离值为最小的顶点W且不在S中,加入S

3. 对其余T中顶点的距离值进行修改:若加进W作中间顶点,从V0到Vi的距离值缩短,则修改此距离值

重复上述步骤2、3,直到S中包含所有顶点,即W=Vi为止

Dijkstra_Animation

详细:Dijkstra算法

算法九:动态规划算法

动态规划(Dynamic programming)是一种在数学、计算机科学和经济学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。 动态规划常常适用于有重叠子问题和最优子结构性质的问题,动态规划方法所耗时间往往远少于朴素解法。

动态规划背后的基本思想非常简单。大致上,若要解一个给定问题,我们需要解其不同部分(即子问题),再合并子问题的解以得出原问题的解。 通常许多子问题非常相似,为此动态规划法试图仅仅解决每个子问题一次,从而减少计算量: 一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题解之时直接查表。 这种做法在重复子问题的数目关于输入的规模呈指数增长时特别有用。

关于动态规划最经典的问题当属背包问题。

算法步骤:

1. 最优子结构性质。如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。最优子结构性质为动态规划算法解决问题提供了重要线索。

2. 子问题重叠性质。子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只计算一次,然后将其计算结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只是在表格中简单地查看一下结果,从而获得较高的效率。

详细参考:

从全球导航到输入法:谈谈动态规划

动态规划

算法十:朴素贝叶斯分类算法

朴素贝叶斯分类算法是一种基于贝叶斯定理的简单概率分类算法。贝叶斯分类的基础是概率推理,就是在各种条件的存在不确定,仅知其出现概率的情况下,如何完成推理和决策任务。概率推理是与确定性推理相对应的。而朴素贝叶斯分类器是基于独立假设的,即假设样本每个特征与其他特征都不相关。

朴素贝叶斯分类器依靠精确的自然概率模型,在有监督学习的样本集中能获取得非常好的分类效果。在许多实际应用中,朴素贝叶斯模型参数估计使用最大似然估计方法,换言之朴素贝叶斯模型能工作并没有用到贝叶斯概率或者任何贝叶斯模型。

尽管是带着这些朴素思想和过于简单化的假设,但朴素贝叶斯分类器在很多复杂的现实情形中仍能够取得相当好的效果。

详细参考:

贝叶斯网络

朴素贝叶斯分类算法

本文链接:程序员必须知道的10大基础实用算法及其讲解

出处:快课

转载请保留出处链接,谢谢!

摘自:http://www.cricode.com/2001.html

 

Vysor – 无需 root,用 Chrome 完全控制 Android 设备

Vysor是一款 Chrome 应用,能够在 Chrome 里通过 USB 直接控制 Android 设备,无需 root。@Appinn

Vysor 的设置非常简单,只需要在 Chrome 里安装 Vysor,打开 Android 设备的 USB 调试模式,用 USB 线连接电脑与 Android 设备,就可以控制了。

在 Chrome 里,你能实时看到 Android 的界面,并且可以通过鼠标拖动、点击屏幕,还有其他快捷键:

  • ESC 返回按钮
  • F1 菜单按钮
  • Home 返回桌面
  • 鼠标右键 返回按钮
  • 鼠标中建桌面

第一次连接 Vysor,会自动在 Android 设备中安装一个应用,然后点击信任连接的电脑,就能在 Chrome 操作了,非常简单。

而在连接成功后,你完全可以将 Android 设备放到一边,Vysor 支持点亮屏幕(只需在黑屏上点一下鼠标右键),滑动输入密码,键盘打字,如果再配合PushBullet 将系统通知推送到 Chrome 里,真的 Android 与 电脑合一了…

鉴于需要安装 Chrome 应用,推荐个工具访问 Chrome 网上应用店。

青小蛙使用体验,Chrome 端的显示比较顺滑,处于完全可以接受的状态,两端几乎实时同步,没有延时。

Android Studio常用快捷键

[Android Studio] Android Studio常用快捷键

(会持续更新)这边讲的常用快捷键是指做完Keymap到Eclipse后的,不是纯Android Studio的,这边主要讲下比较常用的一些快捷键:

Ctrl+O:快捷查找当前类中的函数,变量

Alt+Enter:导入包

Ctrl+E:查看最近打开过的文件

Ctrl+Shift+E:查看最近编辑过的文件

Ctrl+G / Ctrl+Alt+Shift+G:查询变量或者函数或者类在哪里被使用或被调用,后者是前者的复杂表现,可以选择查询范围等。

Alt+H:查找功能,全局查找

F4:查看类继承关系

F2:查看文档说明(函数使用说明)

double Shift:全局查找,这个查看和Alt+H稍稍有些不同,这个是全局文件查找,到文件名称层面。

Ctrl+Shift+R:快速定位到你所想打开的文件。

Ctrl+K:选中一个变量后,快速定位到下一个使用该变量的地方(不过这个快捷键现在还存在一些bug,具体请看:Android Studio keymap到Eclipse后,查找下一个相同变量快捷键Ctrl+K失效

Alt+↑:光标所在位置那行代码往上移动

Alt+↓:光标所在位置那行代码往下移动

Ctrl+D:删除光标所在位置那行代码

Ctrl+X:剪切光标所在位置那行代码

Alt+Shift+↓/Ctrl+C:复制光标所在行代码到下一行

Ctrl+Shift+R:修改名称

Alt+←:后退,定位到上个查看或者编辑的地方

Alt+→:往前定位,比如你定位到上个点后,想回去,就可以用这个快捷键

Ctrl+/:当行注释,反注释再按一次即可

Ctrl+Shift+/:模块注释,反注释再按一次即可,注意这边的”/“不能用小键盘的

Ctrl+Shift+小键盘/:折叠代码(Ctrl+Shift+小键盘*这个不灵了,今天没空了,后面会针对这个问题做解决,并更新上来),当然笔记本没小键盘,你可以自己改快捷键

Ctrl+Alt+S:打开settings界面

Ctrl+Alt+Shift+S:打开Project Structure界面

Alt+Shift+X:运行(Run)

Alt+Shift+D:调试运行(Debug)

Ctrl+F9:编译工程

Ctrl+Shift+K:push文件到Server(git)

Debug类快捷键

F5:但不调试进入函数内部。

F7:由函数内部返回调用处。

F8:执行到下一个断点,没断点则执行完成。

Ctrl+Alt+F8/双击鼠标:直接查看选中位置的值,这两个快捷键稍稍有点区别,具体区别请看这:Android Studio 调试过程中快捷查看断点处变量值(Ctrl+Shift+I无效)?

这里有一个Android Studio的新的快捷功能,查看当前类中有哪些函数变量没被用到,哪些写法不合理。Eclipse没有的,很有用,下图中Inspect Code with Editor Settings就是是,你可以自己配置你所想用的快捷键,配置过程中,如果发现你想配置的快捷键被使用了,怎么看明白和哪些冲突,怎么解决,请戳这:Android Stuido如何查看快捷键冲突?

分类: Android Pro

摘自:http://www.cnblogs.com/0616–ataozhijia/p/3870064.html

这些小工具让你的Android开发更高效

本文为作者「Tikitoo」投稿,应该多少受我点影响,Tikitoo也是一位自学的Android工程师,并且完全通过自学找到一份还不错的工作。互联网爱好者,并且是简书专题的运营者,点击「阅读原文」可以跳转到作者的博客。

在做Android 开发过程中,会遇到一些小的问题,虽然自己动手也能解决,但是有了一些小工具,解决这些问题就得心应手了,今天就为大家推荐一下Android 开发遇到的小工具,来让你的开发更高效。

1. Vysor

Vysor 是一个可以将手机的屏幕投影到电脑上,当然也可以操作,当我们做分享或者演示的时候,这个工具起到了作用。

2. Vector Asset

Android Studio 在1.4 支持了VectorAsset,所谓VectorAsset;它可以帮助你在Android 项目中添加Material Icon 和SVG 图片来作为一个Drawable 资源来使用。不过唯一一点的缺陷就是没有搜索功能,如果你想精心挑选Material Icon ,可以打来网页版来选择,也可以下载SVG 和Png 格式。对于VectorAsset 的好处,它的文件更小,更容易适配不同的屏幕。

3. Stetho

Stetho 是一个Android 开发调试小工具,它可以让你使用Chrome Develop Tools 来可以来查看Sqlite 数据库和SharePreferences,而且可以查看网络连接的数据。在Chrome 输入框输入「chrome://inspect」,点击inspect 就可以开始了。如果使用OkHttp 需要添加拦截器StethoInterceptor。

4. OctoTree

OctoTree 是一个浏览器插件,它可以让你在Github 看代码时,左边栏会出现一个树状结构,就像我们在IDE 一样。当我们看一个项目的结构,或者想看具体的某个文件,这样就会很方便。

5. Chrome ADB

Chrome ADB 是一个使用Chrome 来调试Android 的小工具,它除了提供了安装,卸载,清理数据的基本功能,而且还提供了主页,返回,锁屏的虚拟键功能,也可以看各个应用占用的内存(不得不点名批评一下微信,关闭都还占用100M 内存,不知道你要干嘛)。它还有Android 的App,两者交互一定更有意思。

6. TinyPng

TinyPng 是一个图片压缩工具,可能有些人感觉这个工具应该给设计师使用,我觉得也是。不过有些时候,设计师给你出了个1920* 1080 的启动页,一张图片,1M 左右,我也是泪奔了,感觉设计师说话的时间,估计我们都压缩完了,自己动手,丰衣足食。而且它还提供了API,对不同语言都还有提供了插件,比如Java 就提供了Maven 的支持。

7. PostMan

PostMan 是一个API 调试工具,它提供Chrome App 和Mac App,除了提供基本的API 测试功能, 它还可以添加各种的Auth 认证,响应结果可以选择不同类型,比如HTML,JSON 等,可以设置通用的Header,还可以将之前测试的添加到一个集合,而且也可以同步到服务器,而且最近还添加了团队服务,想想服务器端写完测试你就能看到结果,而不是给你API 文档(当然API 文档还是要有的),这画面太美,我不敢想象。当然它的功能也远远不止这些,它还有专业版,想尝试更多的东西可以体验一下。

8. Genymotion 虚拟机

刚开始做开发的时候,每次使用官方的虚拟机,都想吐槽一下,但是发现了Genymotion 之后,这一切都变化了,它的速度几乎可以和真机媲美了,当然如果有真机,当然还是推荐使用真机测试。据说官方模拟器2.0 很快,不知道是不是又吹牛逼。

9. Json2POJO

Json2POJO 是可以将一个Json 字符串转换成Java 的POJO 类的网页工具,而且可以选择转换器,比如我们使用Retrofit 可以选择Jackson,Gson,而且可以选择重写get,set 方法,还有hashcode,equals 和toString 方法,可以省去了不少手写的时间。

10. Android Pixel

AndroidPixel 是一个简单的将不同的分辨率的换算工具,只要你有一个尺寸的大小,其他的尺寸大小就可以得出,当然dp 这样的单位,可以解决一部分问题,但是大多还要需要微调,这时AndroidPixel 就起到了作用。这个工具来自上一个公司同事告诉我的。

11. Android Arsenal

Android Arsenal 主要是推荐Github 上一些流行的Android 开源项目,基本上最近热门的Android 开源项目都会出现在这里,它还对不同类库进行了分类。

12. AndroidAssetStudio

Android Asset Studio 是一个在线制作工具,它可以制作Iocn,ActionBar,点9 图等等,简单的操作,大大提高了我们开发的效率。

13. WiFi ADB

WiFi ADB 是一个通过无线网络来使电脑和手机连接的手机App(可以去Google Play 搜索类似的),当我们做测试的时候,只需在手机上打开,电脑只需在命令行输入「adb connect xxx.xxx.xxx.xxx:5555」,电脑可以连接手机,就可以通过无线网络来调试开发的应用。

14. ES Explorer

ES Explorer 是一款文件管理器,但实际它又不仅仅是一款文件管理器,在获得Root 之后,它的功能更强大了,它可以浏览受限制的文件目录;而且提供了一系列小工具,比如下载器;还有集成了众多云储存服务。

微信公众号不支持链接,可以点击「阅读原文」查看。

友情提示:

之前的书籍奖励的两位同学「纪红阳」、「.」,没错,第二个中奖的用户微信名称就是一个点,不知道你们给我回复了收获地址被淹没了还是压根忘记回复了,请加我微信吧「stormzhang」,微信好友已经太多了,其他人一概不加,忘见谅!

 

摘自:http://mp.weixin.qq.com/s?__biz=MzA4NTQwNDcyMA==&mid=402858357&idx=1&sn=5dd38f4dcc5d7680e6daf7b3d1105b63#rd

成功者是如何战胜生命中的忧虑?

繁重的生活、工作压力让每个人都能感受到情绪的存在,人们都想摆脱不良情绪,却在很多时候都被情绪牵着鼻子走。如何能够面对现实,活得开心?今天“读写客”推荐美国成功学大师戴尔 · 卡耐基文章,探讨人性的优点与缺点。

1871年春,一个年轻人拿起一本书,书中21个词汇对他的未来产生了深刻的影响。他是加拿大蒙特利尔综合医院的一个学生。他为期末考试烦恼,为无所事事烦恼,为何去何从烦恼,为怎么实习烦恼,为如何谋生烦恼。

得益于1871年读过的那21个词汇,这位年轻的医学院学生成为他那一代人中最有声望的内科专家。他创建了蜚声世界的霍普金斯医学院,他成为剑桥大学的皇家教授——那是大英帝国授给医学界人士的最高荣誉。他被英国国王封为爵士。他去世时,关于他生平事迹的书籍足有整整两卷,长达1466页。他是威廉·奥斯勒爵士。1871年春他读过的那21个词汇出自托马斯·卡莱尔,这些词汇让他在此后的人生中远离了烦恼:“我们的主要任务不是遥望远方的黑暗,而是关注于眼前。”

42年后一个微风和煦的晚上,耶鲁校园里的郁金香迎着春风盛放,就是这个人——威廉·奥斯勒爵士,对耶鲁学子们发表演说。他告诉这些学生,像他那样一个任教于四所大学、写过一部畅销书的教授,理应有一个“卓尔不凡的大脑”。他宣称,事实并非如此。他说,熟悉他的人都知道,他的脑子“平庸之极”。

那么,什么才是他的成功秘诀呢?他解释说,他的成功归功于他所说的活在“日密舱”里。这话怎么讲?在耶鲁演讲前几个月,威廉·奥斯勒爵士搭乘一艘邮轮横渡大西洋。船长站在驾驶舱里,按动一枚按钮——机器“叮当”一声,瞬间变戏法似的,轮船一些部件立即纷纷关闭,然后轮船进入“水密舱”。

“现在,你们每一人——”威廉·奥斯勒博士对这些耶鲁学子说,“你们每个人的身体都是一部比巨型邮轮更了不起的组织,航程也更远大。我要告诫你们,学会控制,生活在‘日密舱’,这才是航行最安全的保障。去驾驶舱看看,至少确保主要隔水舱运行正常。

在你人生的每一个阶段,按一下按钮,把“过去”——死去的昨天关在外面;接着,按另一个按钮,一个金属舱壁再次关上,那是“未来”——还未诞生的明天;这样你就安全了——你的今天就安全了!

把未来关闭得和过去一样严严实实吧……未来就是今天……永远就没有个什么明天。人类的救赎就在今天。一个对未来担惊受怕的人,精力的损耗、内心的压力和精神上的烦恼会像咬尾的蛇一样紧追不舍……那么,将过去和未来的大水壁关闭了,养成这样一种习惯——活在当下的‘日密舱’中!”

奥斯勒博士的意思莫非是我们完全不必为了明天而未雨绸缪?不,完全不是。在那场演讲中他接着说过,对明天最好的准备方式就是集中你们所有的智慧和热情,把今天的事情做得无可挑剔。这是迎接明天唯一最可行的办法。

在耶鲁大学,威廉·奥斯勒爵士呼吁学生按耶稣基督祷告的那样开始每一天:“把今天的面包给我们。”

记住,这句祷文只要求今天的面包,而没有抱怨我们昨天吃下去的旧面包,也没有说:“主啊,近来小麦带干旱,可能还会干旱,明天秋天我可去哪儿找面包啊?——或者我要是失业了,主啊,我去哪儿找吃的啊!”

没有,这句祷文只教诲我们找今天的面包。因为,今天的面包可能是你唯一能吃到口的。

多年前,一个身无分文的哲学家在一片贫瘠的土地上晃荡,那儿的人生活得很艰辛。一天,人们在一座山丘上围绕着哲学家,他发表了一场演说,在人类历史上其引用率可能空前绝后。他的演说包含26个词汇,回荡了几个世纪之久:“不要去想什么明天,因为明日自有明日的想法,活好今天才是正道。”

很多人反对耶稣的这句话:“别想明天的事情。”他们将这句话斥之为完美建议和神秘主义。他们说:“为了保护家人,我必须买保险;为了养老,我必须攒钱;未来将来,我必须早做打算。”

没错!你当然必须。实际上,耶稣这句话是300年前翻译的,在詹姆斯王朝时的意思和今天的意思并不一样。300年前,thought这个词的意思常常指anxiety(焦虑、忧虑)。现代版《圣经》对耶稣这句话的引用更为准确:“别为明日忧。”

是的,无论如何,想着明天;为了明天未雨绸缪,但别因为明天而烦恼。

第二次世界大战期间,我们的军事领导人为明天做了谋划,但他们无法承受烦恼之重。

“我为最好的将士配备了我们所能提供的最好武器。”指挥美国海军的欧内斯特·J.詹姆斯上将这样说,“还给了他们执行使命的最佳方案,我能做的就是这些。”

“如果一艘舰艇被击沉。”欧内斯特·J.詹姆斯上将接着说,“我没办法把它打捞起来。如果一定会沉没,我也束手无策。我能更有效地为明天的事情工作,而不是为昨天的事情懊恼不已。另外,如果我任由那些烦恼缠住我,我也扛不住。”

无论战争时期还是和平时期,乐观思维和悲观思维最大的区别在于——乐观思维能理顺事情的前因后果,取得符合逻辑、建设性的计划;悲观思维则经常引起紧张和神经崩溃。

我荣幸地采访过阿瑟·海斯·苏兹伯格,1935到1961年间,他是世界上最有影响力的报纸之一《纽约时报》的出版人。苏兹伯格先生告诉我,当“二战”硝烟弥漫欧洲时,他惊慌失措,对未来太焦虑,以至于几乎无法入眠。他常常半夜起床,取出画布和油画笔,看着镜中的自己,试图画一幅自画像。其实他根本不会画画,但为了驱逐焦虑,还是画了。苏兹伯格先生告诉我,他从来无法祛除他的焦虑,直到他将一首赞美诗中的词汇作为自己的座右铭——“一步对我足够”。

光明指引……

我紧随你的脚步:

我勿需眺望远景;

一步对我足够。

大约同一时间欧洲某个地方,一个穿军服的年轻人也学到了人生这一课。他的名字叫特德·本杰明诺,来自马里兰州的巴尔的摩,他的焦虑使他患上了一级战斗疲劳症。

“1945年4月。”本杰明诺写道,“我的焦虑发展到了医生所说的‘痉挛性结肠炎’——那是一种能引起剧烈疼痛的病。如果战争不是那时结束,我整个人肯定早就垮了。”

本杰明诺接着说:“我当时已经筋疲力尽了。我是墓地登记员,隶属于第94步兵师,是没有军衔的那种军官。我的职责就是对在各项行动中所有阵亡、失踪和负伤住院的人员建立档案,并保存这些记录。我还必须协助把那些在战斗最激烈时阵亡后匆匆浅埋的军人尸体——无论是盟军还是敌军的,挖出来进行深埋。我还必须将阵亡军人的个人物品收集起来,负责寄送给他们的家人或近亲;对这些物品,他们非常珍视。

我担心不停,生怕出什么纰漏,引起尴尬。我担心我能否完成这任务。我担心我能不能活到抱一抱我那唯一的儿子的那一刻——他才16个月大,我还没见过呢。我焦虑和疲惫得体重掉了34磅。我狂躁不安,距疯掉只一步之遥。我看着自己的双手,发现它们简直就是皮包骨。一想起回家时成了个废人,我就魂飞魄散。我崩溃了,像孩子似的哭泣。我脆弱极了,一旦独处我就泪流满面。在巴尔吉战役后不久的那一阵,我哭得太多了,连做一个正常人的希望都差点放弃。

“最后我去了部队医院,一位军医给了我一些建议——这些建议彻底改变了我的生活。在做了彻底体检后他告诉我,我的问题是心理上的。他说:‘特德,我希望你把自己的生命看成一个计时沙漏器。你知道,在沙漏器顶部有不计其数的沙粒,所有沙粒都会缓慢而均匀地穿过容器中部的狭窄瓶颈;除非把沙漏弄坏了,你我没有任何办法能多让一粒沙子经过瓶颈。你,我,或任何别的人,都像沙漏器一样。每天早上一起床,我们会觉得有几百个任务需要当天完成;然而,如果我们不按部就班一个一个地来——就像沙粒通过沙漏器的瓶颈一样,慢慢地来,有条不紊地来;不论我们的身体还是心理,都会垮掉的。’

“自从军医给我上了这堂难忘的课那天后,我便开始践行这些哲学。‘一次一粒沙……一次一件事’,这个忠告在战争期间不仅挽救了我的身体,还挽救了我的心灵;对我目前在广告印务和平面印务公司担任的公共关系和广告经理这个职务,也很有帮助。我发现,那些战争时期出现的问题,在职场中同样出现了:太多事情需要马上处理——而时间却太紧。

譬如,库存太低,新表格需要填写,进货需要安排,地址要修改,办公室开张关张,等等。我记起那位军医的话‘一次一粒沙……一次一件事’,才没有方寸大乱。通过不断温故这句话,我以一种有效得多的方式完成了任务,那种在战场上差点毁了我的迷茫和紊乱感烟消云散。”

关于当代生活方式,最令人瞠目结舌的观点之一是:我们的医院一度把一半病床留给了各种精神和心理病人——无数个“昨天”累积起来的千钧重压和令人忧惧的“明天”,终于将这些人压垮。然而,这类病人绝大部分原本并不需要住院,他们原本可以过着幸福有意义的生活——如果他们一早就知道耶稣所说的“别为明日忧”或者威廉·奥斯勒爵士说的“活在日密舱”的道理。

此刻,你我正好同时站在两种永恒的交会点:不堪回首的浩瀚而永恒的历史以及连通永恒的未来。我们不可能置身一种永恒里——不可能!甚至不可能活在被分割的一秒钟之内。如果非要去折腾,必然身心俱毁。所以,还是知足常乐,活在我们唯一能够存在的时间内:从现在起,直到上床就寝。

罗伯特·路易斯·斯蒂文森这样说:“无论负担多么沉重,任何人都能撑一天;无论工作多么艰难,任何人都能坚持一天;从朝霞到日暮,任何人都能生活在甜蜜、耐心、珍爱和纯洁之中。这就是生活的全部要义。”

是的,这就是生活对我们的要求。但对于密歇根州萨基纳多的E.K.谢尔兹太太而言,在学会“活在今天”之前,她陷入了绝望——甚至处于自杀的边缘。谢尔兹太太向我讲述她的故事时,这样说道:

“1937年,我丈夫去世了。我极度抑郁——几乎不名一文。我给我的前雇主——堪萨斯市洛奇福勒公司的里奥·洛奇先生写了一封信,恢复了我以前的工作。以前我是以去乡村和小镇推销《世界百科全书》为生。两年前,我丈夫生病,我卖掉了我的汽车。恢复工作后,我想方设法凑齐了买一辆二手车的首付款。我曾以为,重操旧业有助于缓解我的抑郁症,但孤身一人开车,孤身一人吃饭简直让我难以忍受。有些地方生意不好做,我觉得很难赚够买车钱——尽管那也不算什么大钱。

“1938年春,我去密苏里州的维塞里斯卖书。那儿的学校很穷,路很旧。我如此孤独和沮丧,甚至想到了自杀。我的日子毫无奔头了,我恐惧明天早起面对生活。我担心一切:还不起购车款,付不起房租,吃不起饭,身体日渐垮了却看不起病……只有两个想法阻止了我的自杀:我姐姐会伤心欲绝,我没钱支付我的葬礼。

“后来的一天,我看到的一篇文章将我从沮丧里拽了出来,并给了我活下去的勇气。其中一句话非常鼓舞人,我永远不会忘记文章中这句话对我的恩典。这句话是这样的:‘对聪明人来说,每一天都是新生活。’我把那句话打印下来,粘贴在汽车挡风玻璃上,这样,开车时每一分钟都会看到。我学会了忘记昨天,也不想明天。每天早晨,我都对自己说:‘今天就是新生活。’我发现,每一次活过一天不算太难。

“我战胜了我的孤独恐惧,我的需求恐惧。现在的我很开心,对生活充满了热情和爱。我现在明白,无论今后生活怎么变幻,我都不会再恐惧了;我现在明白,我不必为未来恐惧;我现在明白,我能一次活好一天;我现在明白‘对聪明人来说,每一天都是新生活’的含义。”

底特律不久前去世的爱德华·S.伊文斯先生,在领悟“生活就是活在每一天、每一分钟的肌理中”这句话之前,差一点杀了自己。爱德华·S.伊文斯幼时家境贫寒,他挣的第一分钱是做报童,随后在杂货店打工。再后来,为了养活家里七个人,他去做图书馆助理管理员。尽管薪水少得可怜,他却担心丢掉饭碗。八年后,他终于鼓起勇气创业。

但一旦开始,他凭东拼西凑的50美元老本,仅在第一年就赚了两万美元!然后是一场变故,一场要命的变故。他为一个朋友做贷款担保,这个朋友却破产了。很快,祸不单行——他存款的那家银行倒闭了。爱德华除了将赚到的每一分钱全部亏掉,还倒欠一万六!他无法承受这个打击。

他告诉我:“我寝食难安,我很奇怪地得了一种病——焦虑症。焦虑,我的病完全是焦虑引起的。有一天当我走在街上时,走不动了,昏倒在人行道上。我被送到病床上,我身上长满了大疡肿。大疡肿向体内发展,直到我一病不起,备受折磨。我的身体一天天虚弱下去。最后,大夫告诉我只有两个礼拜可活。我吓坏了,写好遗书,躺回病床等待末日来临。现在,挣扎和焦虑都无济于事。于是,我放弃了,放松了,睡着了。此前几个礼拜,我没有一天睡满过两小时;现在呢,反正尘世间的一切都已了结,我睡得跟个婴儿似的。我的精疲力竭开始逆向发展,我的胃口回来了,我的体重居然增加了。

“几周后,我能拄着拐杖走路了。六周后,我就回去工作了。我曾经一年赚了两万美元,现在找了个每周付我30美元的工作我也很开心。我的工作是销售——汽车装船运输时,需要在车后轮上装阻隔物——我就卖那种阻隔物。我现在汲取了以前的教训。对过去,我无怨无悔;对将来,也从容坦荡。我将自己所有的时间、精力和热情全部倾注于销售工作中。”

爱德华·S.伊文斯的事业从此突飞猛进。几年后,他成为伊文斯产品公司老总,公司在纽约证券交易所上市好几年了。如果你飞临格陵兰岛,可能会降落在以他名字命名的伊文斯机场。当然,如果伊文斯不能领悟“活在‘日密舱’”的含义,他绝不会取得这些成就。

你可能会想起怀特·奎恩说过的那句话:“给你昨天的果酱,给你明天的果酱,但今天的果酱想都别想。”

此句话比喻那些永远无法实现的诺言和空头支票。

我们大多数人都喜欢为昨天耿耿于怀,为明天惴惴不安,却从来不抓住眼前的醇厚的“果酱”。

本文选自《人性:优点与弱点》,北京大学出版社

淘宝大秒系统设计详解

导读:最初的秒杀系统的原型是淘宝详情上的定时上架功能,由于有些卖家为了吸引眼球,把价格压得很低。但这给的详情系统带来了很大压力,为了将这种突发流量隔离,才设计了秒杀系统,文章主要介绍大秒系统以及这种典型读数据的热点问题的解决思路和实践经验。

一些数据

大家还记得2013年的小米秒杀吗?三款小米手机各11万台开卖,走的都是大秒系统,3分钟后成为双十一第一家也是最快破亿的旗舰店。经过日志统计,前端系统双11峰值有效请求约60w以上的QPS ,而后端cache的集群峰值近2000w/s、单机也近30w/s,但到真正的写时流量要小很多了,当时最高下单减库存tps是红米创造,达到1500/s。

热点隔离

秒杀系统设计的第一个原则就是将这种热点数据隔离出来,不要让1%的请求影响到另外的99%,隔离出来后也更方便对这1%的请求做针对性优化。针对秒杀我们做了多个层次的隔离:

  • 业务隔离。把秒杀做成一种营销活动,卖家要参加秒杀这种营销活动需要单独报名,从技术上来说,卖家报名后对我们来说就是已知热点,当真正开始时我们可以提前做好预热。
  • 系统隔离。系统隔离更多是运行时的隔离,可以通过分组部署的方式和另外99%分开。秒杀还申请了单独的域名,目的也是让请求落到不同的集群中。
  • 数据隔离。秒杀所调用的数据大部分都是热数据,比如会启用单独cache集群或MySQL数据库来放热点数据,目前也是不想0.01%的数据影响另外99.99%。

当然实现隔离很有多办法,如可以按照用户来区分,给不同用户分配不同cookie,在接入层路由到不同服务接口中;还有在接入层可以对URL的不同Path来设置限流策略等。服务层通过调用不同的服务接口;数据层可以给数据打上特殊的标来区分。目的都是把已经识别出来的热点和普通请求区分开来。

动静分离

前面介绍在系统层面上的原则是要做隔离,接下去就是要把热点数据进行动静分离,这也是解决大流量系统的一个重要原则。如何给系统做动静分离的静态化改造我以前写过一篇《高访问量系统的静态化架构设计》详细介绍了淘宝商品系统的静态化设计思路,感兴趣的可以在《程序员》杂志上找一下。我们的大秒系统是从商品详情系统发展而来,所以本身已经实现了动静分离,如图1。

图片描述

图1 大秒系统动静分离
除此之外还有如下特点:

  • 把整个页面Cache在用户浏览器
  • 如果强制刷新整个页面,也会请求到CDN
  • 实际有效请求只是“刷新抢宝”按钮

这样把90%的静态数据缓存在用户端或者CDN上,当真正秒杀时用户只需要点击特殊的按钮“刷新抢宝”即可,而不需要刷新整个页面,这样只向服务端请求很少的有效数据,而不需要重复请求大量静态数据。秒杀的动态数据和普通的详情页面的动态数据相比更少,性能也比普通的详情提升3倍以上。所以“刷新抢宝”这种设计思路很好地解决了不刷新页面就能请求到服务端最新的动态数据。

基于时间分片削峰

熟悉淘宝秒杀的都知道,第一版的秒杀系统本身并没有答题功能,后面才增加了秒杀答题,当然秒杀答题一个很重要的目的是为了防止秒杀器,2011年秒杀非常火的时候,秒杀器也比较猖獗,而没有达到全民参与和营销的目的,所以增加的答题来限制秒杀器。增加答题后,下单的时间基本控制在2s后,秒杀器的下单比例也下降到5%以下。新的答题页面如图2。

图片描述

图2 秒答题页面
其实增加答题还有一个重要的功能,就是把峰值的下单请求给拉长了,从以前的1s之内延长到2~10s左右,请求峰值基于时间分片了,这个时间的分片对服务端处理并发非常重要,会减轻很大压力,另外由于请求的先后,靠后的请求自然也没有库存了,也根本到不了最后的下单步骤,所以真正的并发写就非常有限了。其实这种设计思路目前也非常普遍,如支付宝的“咻一咻”已及微信的摇一摇。

除了在前端通过答题在用户端进行流量削峰外,在服务端一般通过锁或者队列来控制瞬间请求。

数据分层校验

图片描述

图3 分层校验
对大流量系统的数据做分层校验也是最重要的设计原则,所谓分层校验就是对大量的请求做成“漏斗”式设计,如图3所示:在不同层次尽可能把无效的请求过滤,“漏斗”的最末端才是有效的请求,要达到这个效果必须对数据做分层的校验,下面是一些原则:

  • 先做数据的动静分离
  • 将90%的数据缓存在客户端浏览器
  • 将动态请求的读数据Cache在Web端
  • 对读数据不做强一致性校验
  • 对写数据进行基于时间的合理分片
  • 对写请求做限流保护
  • 对写数据进行强一致性校验

秒杀系统正是按照这个原则设计的系统架构,如图4所示。

图片描述

图4 秒杀系统分层架构
把大量静态不需要检验的数据放在离用户最近的地方;在前端读系统中检验一些基本信息,如用户是否具有秒杀资格、商品状态是否正常、用户答题是否正确、秒杀是否已经结束等;在写数据系统中再校验一些如是否是非法请求,营销等价物是否充足(淘金币等),写的数据一致性如检查库存是否还有等;最后在数据库层保证数据最终准确性,如库存不能减为负数。

实时热点发现

其实秒杀系统本质是还是一个数据读的热点问题,而且是最简单一种,因为在文提到通过业务隔离,我们已能提前识别出这些热点数据,我们可以提前做一些保护,提前识别的热点数据处理起来还相对简单,比如分析历史成交记录发现哪些商品比较热门,分析用户的购物车记录也可以发现那些商品可能会比较好卖,这些都是可以提前分析出来的热点。比较困难的是那种我们提前发现不了突然成为热点的商品成为热点,这种就要通过实时热点数据分析了,目前我们设计可以在3s内发现交易链路上的实时热点数据,然后根据实时发现的热点数据每个系统做实时保护。 具体实现如下:

  • 构建一个异步的可以收集交易链路上各个中间件产品如Tengine、Tair缓存、HSF等本身的统计的热点key(Tengine和Tair缓存等中间件产品本身已经有热点统计模块)。
  • 建立一个热点上报和可以按照需求订阅的热点服务的下发规范,主要目的是通过交易链路上各个系统(详情、购物车、交易、优惠、库存、物流)访问的时间差,把上游已经发现的热点能够透传给下游系统,提前做好保护。比如大促高峰期详情系统是最早知道的,在统计接入层上Tengine模块统计的热点URL。
  • 将上游的系统收集到热点数据发送到热点服务台上,然后下游系统如交易系统就会知道哪些商品被频繁调用,然后做热点保护。如图5所示。

图片描述

图5 实时热点数据后台
重要的几个:其中关键部分包括:

  • 这个热点服务后台抓取热点数据日志最好是异步的,一方面便于做到通用性,另一方面不影响业务系统和中间件产品的主流程。
  • 热点服务后台、现有各个中间件和应用在做的没有取代关系,每个中间件和应用还需要保护自己,热点服务后台提供一个收集热点数据提供热点订阅服务的统一规范和工具,便于把各个系统热点数据透明出来。
  • 热点发现要做到实时(3s内)。

关键技术优化点

前面介绍了一些如何设计大流量读系统中用到的原则,但是当这些手段都用了,还是有大流量涌入该如何处理呢?秒杀系统要解决几个关键问题。

Java处理大并发动态请求优化

其实Java和通用的Web服务器相比(Nginx或Apache)在处理大并发HTTP请求时要弱一点,所以一般我们都会对大流量的Web系统做静态化改造,让大部分请求和数据直接在Nginx服务器或者Web代理服务器(Varnish、Squid等)上直接返回(可以减少数据的序列化与反序列化),不要将请求落到Java层上,让Java层只处理很少数据量的动态请求,当然针对这些请求也有一些优化手段可以使用:

  • 直接使用Servlet处理请求。避免使用传统的MVC框架也许能绕过一大堆复杂且用处不大的处理逻辑,节省个1ms时间,当然这个取决于你对MVC框架的依赖程度。
  • 直接输出流数据。使用resp.getOutputStream()而不是resp.getWriter()可以省掉一些不变字符数据编码,也能提升性能;还有数据输出时也推荐使用JSON而不是模板引擎(一般都是解释执行)输出页面。

同一商品大并发读问题

你会说这个问题很容易解决,无非放到Tair缓存里面就行,集中式Tair缓存为了保证命中率,一般都会采用一致性Hash,所以同一个key会落到一台机器上,虽然我们的Tair缓存机器单台也能支撑30w/s的请求,但是像大秒这种级别的热点商品还远不够,那如何彻底解决这种单点瓶颈?答案是采用应用层的Localcache,即在秒杀系统的单机上缓存商品相关的数据,如何cache数据?也分动态和静态:

  • 像商品中的标题和描述这些本身不变的会在秒杀开始之前全量推送到秒杀机器上并一直缓存直到秒杀结束。
  • 像库存这种动态数据会采用被动失效的方式缓存一定时间(一般是数秒),失效后再去Tair缓存拉取最新的数据。

你可能会有疑问,像库存这种频繁更新数据一旦数据不一致会不会导致超卖?其实这就要用到我们前面介绍的读数据分层校验原则了,读的场景可以允许一定的脏数据,因为这里的误判只会导致少量一些原本已经没有库存的下单请求误认为还有库存而已,等到真正写数据时再保证最终的一致性。这样在数据的高可用性和一致性做平衡来解决这种高并发的数据读取问题。

同一数据大并发更新问题

解决大并发读问题采用Localcache和数据的分层校验的方式,但是无论如何像减库存这种大并发写还是避免不了,这也是秒杀这个场景下最核心的技术难题。

同一数据在数据库里肯定是一行存储(MySQL),所以会有大量的线程来竞争InnoDB行锁,当并发度越高时等待的线程也会越多,TPS会下降RT会上升,数据库的吞吐量会严重受到影响。说到这里会出现一个问题,就是单个热点商品会影响整个数据库的性能,就会出现我们不愿意看到的0.01%商品影响99.99%的商品,所以一个思路也是要遵循前面介绍第一个原则进行隔离,把热点商品放到单独的热点库中。但是无疑也会带来维护的麻烦(要做热点数据的动态迁移以及单独的数据库等)。

分离热点商品到单独的数据库还是没有解决并发锁的问题,要解决并发锁有两层办法。

  • 应用层做排队。按照商品维度设置队列顺序执行,这样能减少同一台机器对数据库同一行记录操作的并发度,同时也能控制单个商品占用数据库连接的数量,防止热点商品占用太多数据库连接。
  • 数据库层做排队。应用层只能做到单机排队,但应用机器数本身很多,这种排队方式控制并发仍然有限,所以如果能在数据库层做全局排队是最理想的,淘宝的数据库团队开发了针对这种MySQL的InnoDB层上的patch,可以做到数据库层上对单行记录做到并发排队,如图6所示。

图片描述

图6 数据库层对单行记录并发排队
你可能会问排队和锁竞争不要等待吗?有啥区别?如果熟悉MySQL会知道,InnoDB内部的死锁检测以及MySQL Server和InnoDB的切换会比较耗性能,淘宝的MySQL核心团队还做了很多其他方面的优化,如COMMIT_ON_SUCCESS和ROLLBACK_ON_FAIL的patch,配合在SQL里面加hint,在事务里不需要等待应用层提交COMMIT而在数据执行完最后一条SQL后直接根据TARGET_AFFECT_ROW结果提交或回滚,可以减少网络的等待时间(平均约0.7ms)。据我所知,目前阿里MySQL团队已将这些patch及提交给MySQL官方评审。

大促热点问题思考

以秒杀这个典型系统为代表的热点问题根据多年经验我总结了些通用原则:隔离、动态分离、分层校验,必须从整个全链路来考虑和优化每个环节,除了优化系统提升性能,做好限流和保护也是必备的功课。

除去前面介绍的这些热点问题外,淘系还有多种其他数据热点问题:

  • 数据访问热点,比如Detail中对某些热点商品的访问度非常高,即使是Tair缓存这种Cache本身也有瓶颈问题,一旦请求量达到单机极限也会存在热点保护问题。有时看起来好像很容易解决,比如说做好限流就行,但你想想一旦某个热点触发了一台机器的限流阀值,那么这台机器Cache的数据都将无效,进而间接导致Cache被击穿,请求落地应用层数据库出现雪崩现象。这类问题需要与具体Cache产品结合才能有比较好的解决方案,这里提供一个通用的解决思路,就是在Cache的client端做本地Localcache,当发现热点数据时直接Cache在client里,而不要请求到Cache的Server。
  • 数据更新热点,更新问题除了前面介绍的热点隔离和排队处理之外,还有些场景,如对商品的lastmodifytime字段更新会非常频繁,在某些场景下这些多条SQL是可以合并的,一定时间内只执行最后一条SQL就行了,可以减少对数据库的update操作。另外热点商品的自动迁移,理论上也可以在数据路由层来完成,利用前面介绍的热点实时发现自动将热点从普通库里迁移出来放到单独的热点库中。

按照某种维度建的索引产生热点数据,比如实时搜索中按照商品维度关联评价数据,有些热点商品的评价非常多,导致搜索系统按照商品ID建评价数据的索引时内存已经放不下,交易维度关联订单信息也同样有这些问题。这类热点数据需要做数据散列,再增加一个维度,把数据重新组织。

作者简介:许令波,2009年加入淘宝,目前负责商品详情业务和稳定性相关工作,长期关注性能优化领域,参与了淘宝高访问量Web系统主要的优化项目,著有《深入分析Java Web技术内幕》一书。个人网站http://xulingbo.net

一些基于Kafka Connect的开源连接器

在Apache Kafka 0.9版本中,Kafka Connect特性被添加让Kafka可以建立可扩展和安全的流数据管道。下表中,Kafka Connector Hub列出了一些基于Kafka Connect建立的connectors。如果你发现新的,也可以联系confluent-platform@googlegroups.com添加。

Connector Name Owner Status
HDFS confluent-platform@googlegroups.com Confluentsupported
JDBC confluent-platform@googlegroups.com Confluentsupported
Debezium – CDC Sources debezium@gmail.com Community project
MongoDB Source a.patelli@reply.de
a.topchyan@reply.de
In progress
MQTT Source tomasz.pietrzak@evok.ly Community project
MySQL Binlog Source wushujames@gmail.com In progress
Twitter Source rollulus@xs4all.nl In progress
Cassandra Sink Cassandra Sink Community project
Elastic Search Sink ksenji@gmail.com Community project
Elastic Search Sink hannes.stockner@gmail.com In progress
Elastic Search Sink a.patelli@reply.de
a.topchyan@reply.de
In progress

责编/仲浩

摘自:http://geek.csdn.net/news/detail/57267

Git 常用命令

1. 超实用 Alias

alias g="git"
alias gb="git branch"
alias gco="git checkout"
alias gcmsg="git commit -m"
alias gamend="git commit --amend -C HEAD"
alias gst="git status"
alias log="git log --oneline --graph --decorate --color=always"
alias logg="git log --graph --all --format=format:'%C(bold blue)%h%C(reset) - %C(bold green)(%ar)%C(reset) %C(white)%s%C(reset) %C(bold white)—     %an%C(reset)%C(bold yellow)%d%C(reset)' --abbrev-commit --date=relative"

 

2. 取回远端 master 与本地 master 分支合并

gco master

g fetch --all 或者
g fetch origin master

g reset --hard origin/master(本地没有修改,所以完全覆盖也没关系) 或者
g rebase origin/master(本地有修改还没push)

 

3. 推送分支到远端

假设现在所在的分支是 import,指定推送到远端分支 codecloud-import

g push origin import:codecloud-import

假如远端的 liujin-import 分支已经不需要,可以直接覆盖掉

g push -f origin import:codecloud-import

 

4. 追加文件到某个 commit

有时候修完某功能并提交了 commit 之后才发现还有一点小修改,这时候又不想再提交一个commit,可以追加这个文件到前一个commit,步骤如下:

it add 你要追加修改的文件
git commit --amend -C HEAD 或者 gamend

 

5. 查找包含某文件的 commit

git log 文件路径
git show commit_id

or

git log --follow filename(绝对路径)

 

6. 把一个 commit 分拆为两个 commit

老大常说要养成一个小改动对应一个commit的习惯,但是有时候写得太乱懒得去分割就把很多改动做成了一个commit,这样子增加了以后维护的难度,所以要把一个 commit 分拆为多个 commit 怎么办呢?

首先把你要分拆的 file reset 了:

git reset HEAD~1 path/to/file
# This doesn't delete your changes to path/to/file

接着修改当前这个 commit 的 message,命令是:

git commit --amend -v
# -v参数是打开editor编辑

然后就可以把 reset 出来那个 file 新建一个 commit,命令是:

git commit -v path/to/file

 

7. 删除某些 commit

git rebase -i HEAD~10

 

8. 追加修改到之前某个 commit

假如 gst 发现已经有文件被修改,这时候需要把修改暂存起来。

git stash

接着找到你需要追加修改的那个commit id,如4b739bb

g rebase 4b739bb~ -i 或者
g rebase -i HEAD~5 #列出最近5个commit

这时候会自动打开编辑器,把你需要修改的 commit 前面的 pick 改成 edit,保存,关闭编辑器,这时候会回到终端,再输入:

g stash pop

把暂存的修改读出来,然后做修改,g add .,最后

g rebase --continue

 

9. 查找含有特定关键字的 commit

git log –grep
最基本的用法
git log –grep=frotz –grep=nitfol –since=1.month
查找一个月以内commit log message里含有 frotz 或者 nitfol 的 commits

git log –grep=frotz –author=Linus
查找指定作者

git grep -l -e frotz –and -e nitfol
查找同一行含有 frotz 和 nitfol 的文件

git grep -l –all-match -e frotz -e nitfol
查找文件里面含有 frotz 和 nitfol 的文件(不局限于同一行)

 

10. 清空 git working copy 还没追踪的文件

git clean -f

git clean -f -d
如果还想删除目录

git clean -f -X
如果只是想删除忽略的文件

git clean -f -x
如果想删除忽略和非忽略的文件

 

11. 清理本地仓库

长时间做一个项目,经常需要 git fetch,这样做每次都会拉回远端的全部分支。
即使远端有些分支已经删除,但是运行git branch -a还是会显示已删除的分支,
长时间下来这个列表就会很长很长,这时候就需要清理一下本地的仓库了:

git remote prune origin
# `prune`会删除任何不存在于远端仓库的分支,这样运行 `git branch -a`命令的时候顿时就清静了

 

12. 创建追踪远端分支的本地分支

gb dev origin/r1-dev
#Branch dev set up to track remote branch r1-dev from origin.

13. 分支移动

g branch -f master HEAD~3
# 把 master 分支强制移到 HEAD 前面第三个 commit

 

14. Revert一个 Merge

git revert -m 1 M -> W

---o---o---o---M---x---x---W
              /
      ---A---B

 

15. 获取短的 commit hash

git rev-parse --short HEAD

 

16. commit 了以后才记起来忘了创建 .gitignore, 垃圾文件都已经提交

比如说一个nodejs项目,一开始的时候就忘记了创建.gitnore文件忽略掉node_modules的内容,所以其中的内容就已经被提交了。

即使紧接着你在.gitignore加了一行node_modules, 已经被提交的文件是不会自动删除的。

这时候你就需要做的就是:

git rm --cached path/to/ignored
#nodejs案例就是
git rm -r --cached node_modules

 

17. 提交所有被删除的文件

$ git add -u

这个命令告诉 git 自动更新已跟踪的文件, 包括被删除的已跟踪文件。

如果你用的是 git 2.0

$ git add -u :/

友情提示:从 git 2.0(2013年中)开始,以上命令会 stage 整个 working tree 的文件。
如果你只是想 stage 当前目录的文件,应该这么用:

$ git add -u .

 

18. 撤销上一次 git add . 操作

这种情况通常是因为忘记添加.gitignore文件,或者一时手快把一些非必要的文件(如node_modules)跟踪了, 解决办法:

git reset

该命令会 unstage 你上一个 commit 增加的所有文件。

如果你只想 unstage 某些文件:

git reset --

还可以 unstage 文件里某处的改动:

git reset -p

摘自:http://finalshares.com/read-7236?jike-24

要写易删除,而不易扩展的代码

英文来源:Write code that is easy to delete, not easy to extend
作者:tef,拥有着犀利的演讲风格,简介请详见他在http://programmingisterrible.com/about上的自我介绍。

译者简介:张咏枫,硅谷创业公司 BloomSky Inc. 软件工程师,加州大学圣克鲁兹分校计算机科学硕士,方向为机器学习。硕士期间曾在 Palo Alto Networks 实习,从事平台开发。在目前的公司做全栈开发,包括后端 API 开发、分布式计算平台的架构、与其它应用平台的整合、数据库架构,以及前端网页应用的开发。涉及到的技术包括:Django、Celery、Nginx、Node.js、React.js、Rethinkdb 等。个人博客:http://blog.yongfengzhang.com/cn/

感谢译者张咏枫对本篇文章的翻译以及授权。

译者序


本文托管在 GitHub 上: https://github.com/freedombird9/code-easy-to-delete,欢迎 Star 或纠错。


好的文章总是见解独到,功底深厚而逻辑清晰。这是一篇关于如何设计、架构代码的文章。文章的观点新颖而有力。作者的观点是,我们所做的一切 —— 重构、模块化、分层,等等,都是为了让我们的代码易于被删改,都是为了让遗留代码不成为我们的负担,而不是为了代码复用。

作者认为,经过七个不同的开发阶段,最终便可以提炼出这样的代码。每个阶段都有详细的介绍和例子。

初读文章,可能会有抽象、晦涩之感。但多读几遍之后,其主旨就会变的清晰。

一个晚上的彻夜不眠,有了这篇中文翻译,与大家分享,希望对读者有所助益。

本文托管在 GitHub 上,水平有限,还望大家多多指点。

感谢

谢谢秋兄将这篇文章分享给我。

中文翻译如下

编程是一件很糟糕的事 —— 在荒废了自己的一生之后所学到的东西

要写容易删除,而不容易扩展的代码。


没有一行代码产生于理性、有很强的可维护性,且不会被偶然地删除掉 Jean-Paul Sartre’s Programming in ANSI C.


每写一行代码,都会有一个代价:维护。为了不在代码上花费太多,我们有了可复用的软件。但是代码复用有一个问题:当你以后想要修改的时候它就会成为一个障碍。

一个 API 的用户越多,为了引入修改而需要重写的代码就越多。相似的,你依赖第三方 API 越多,当其有任何改变时你的麻烦就越多。管理代码之间的兼容性,或者模块之间的依赖关系在大型系统中是一个很重要的问题。而且随着项目越来越久,这个问题就会变得越复杂。


今天我的观点是,如果我们要去计算一个程序有多少行代码,我们不应该将其看成是「产生了多少行」,而应该看成「耗费了多少行。」 EWD 1036


如果我们将「有多少行代码」看成是「耗费了多少行代码」的话,那么当我们删除这些代码的时候,我们就降低了维护成本。我们应该努力开发可丢弃的(disposable)软件,而不是可复用的软件。

我不需要告诉你删除代码比写代码更有趣吧。

为了写易于删除的代码:重复你自己以避免产生模块依赖性,但是不要重复管理这些代码。同时将你的代码分层:在易于实现但不易于使用的模块的基础上构建易于使用的 API。拆分你的代码:将很难于实现且很可能会改变的模块互相隔离,并同时和其他的模块隔离。不要将每一个选项都写死,容许在运行时做改变。不要试图同时去做上述所有的事情,或许你在一开始就不要写这么多代码。

阶段0:不写代码

代码有多少行本身并不能告诉我们什么,但是代码行数的数量级可以:50,500,5000,10000,25000等等。一个一百万行的庞然大物显然会比一个一万行的程序更折磨人。替代它也会显著花费更多的时间、金钱和努力。

虽然代码越多,摒弃起来就越困难,但是少写一行代码本身并不能省掉任何事情。

即使如此,最容易删除的代码是你一开始就避免写出来的代码。

阶段1:复制粘贴代码

写可复用的代码是一件在事后有了代码库中的使用示例后更容易做的事情,而不是在事前就能预料好的。往好的看,仅仅是利用文件系统你或许就已经在复用很多代码了,所以何必这么担心呢?一点点冗余是健康的。

复制粘贴代码若干次,而不是仅仅为了给这个用法取一个名字就去写一个库函数,是完全没有问题的。一旦把一个东西变成共享的 API,改变起来就会更困难。

调用你的函数的那段代码会依赖于其实现背后有意或无意的行为。使用你的函数的程序员不会根据你的文档去调用,而会根据他们观察到的函数行为去调用。

删除函数内的代码比删除一个函数更简单。

阶段2:不要复制粘贴代码

当你已经复制粘贴足够多次数时,或许就是该提炼出一个函数的时候了。这是「把我从标准库中拯救出来」的东西:「打开一个配置文件并返回一个哈希表」,「删除这个文件夹」。这些例子包括了无状态函数,或者有一些全局信息,如环境变量的函数。这些是最终会出现在一个叫做 “util” 文件中的东西。

旁白:建一个 util 文件夹,把不同的功用放在不同的文件里。单个 util 文件总是会不断变大直到大得来无法拆分。使用单个 util 文件是不简洁的做法。

对于应用或者项目而言通用性越强的代码,就越容易复用,被改变或者删除的可能性就越低。它们包括日志记录,第三方 API,文件柄(handle)或者进程相关的库。其他你不会删除掉的代码有列表、哈希表,以及其他集合。这不是因为它们的接口通常都很简单,而是因为它们的作用域不会随着时间的增长而变大。

我们要努力将代码中难以删除的部分与易于删除的部分分隔得尽可能开,而不是使所有代码都变得易于删除。

阶段3:写更多的模版

虽然我们通过库来避免复制粘贴,但是我们常常会需要复制粘贴来使用这些库,最后导致写了更多的代码。不过我们给这些代码另外一个名字:模版(boilerplate)。模版和复制粘贴在很大程度上很像,除了每次使用模版的时候都会在不同的地方做一些改变,而不是一次次重复完全一样的东西。

就像复制粘贴一样,我们会重复部分代码以避免引入依赖性,以获得灵活度,代价则是冗余。

需要模版的库通常有网络协议、有线格式(wire formats)、解析套件,或者很难将策略(一个程序应该做的)和协议(一个程序能做的)交织起来而又不限制可选项的东西。这种代码是很难被删除的:与其他的电脑通信或者处理不同的文件通常是一种必需,而我们永远不想让业务逻辑充斥其中。

写模版不是在练习代码复用:我们尽可能将变化频繁的部分和相对更稳定的部分分隔开。应最小化库的依赖性或责任,即使我们必须通过模版来使用它们。

你会写更多的代码,但是这些多出来的代码都是在易于删除的部分。

阶段4:不要写模版

当库需要迎合所有要求的时候,模版的作用最为明显。但是有时候重复的东西太多了。是时候将一个弹性很大的库用一个考虑到了策略、流程和状态的库打包起来了。开发易用的 API 就是将模版转换成一个库。

这比你想象中的要普遍:最为流行和倍受喜爱的 Python http 客户端模块 requests 就是一个很成功的例子,它将一个使用起来更为繁琐的库 urllib3 打包,为用户提供了一套更加简单的接口。当使用 http 的时候, requests 照顾到普遍的工作流,而对用户隐藏了许多实际的细节。相比而言, urllib3 处理流水线和连接管理,不对用户隐藏任何细节。

当把一个库包进另一个库的时候,与其说是为了隐藏细节,倒不如说是为了将不同的关切分开: requests 是关于http的冒险,urllib3 则是给你工具让你自己选择你自己的冒险。

我并不是主张让你去建一个 /protocol/ 和 /policy/ 文件夹,但是你确实应该尝试使 util 不受业务逻辑的干扰,并且在易于实现的库的基础上开发易于使用的库。你并不需要将一个库全部写完之后再在上面写另一个库。

将一个第三方库打包起来通常也是很好的实践,即使它们不是协议类的库。你可以写一个适合你的代码的库,而不是在整个项目中都锁定一个选择。开发一个好用的 API 和开发一个具有扩展性的 API 通常是互相冲突的。

像这样将不同的关切分开,能让我们在使一些用户很高兴的同时不会让其他用户想做的事情变得不可能。当你从一开始就有一个好的 API 的时候,分层是最简单的。但是在一个写得不好的 API 上开发出一个好的 API 则会很困难。好的 API 在设计之时就会站在使用者的位置上考虑问题,而分层则是我们意识到我们不能同时让所有人都高兴。

分层更多的是为了使那些很难删除的代码易于使用(在不让业务逻辑污染它们的情况下),而不仅仅是关于写以后可以删除的代码。

阶段5:写一大段代码

你已经复制粘贴了,你已经重构了,你已经分层了,你已经构建了,但是代码在最后还是需要做一些事情的。有时候最好的做法是放弃,然后写一大段垃圾代码将剩余部分弄在一起。

业务逻辑是那种有着无尽的边界情况和快速而肮脏的hack的代码。这是没问题的,我对此并不反对。其他的风格,如「游戏代码」,或者「创始人代码」,也是同一个东西:采用捷径来节省大量的时间。

原因?有时候删掉一个大的错误比删掉18个小的交错在一起的错误更为容易。大量的编程都是探索性的,犯几次错误然后去迭代比想着一开始就做对更快速。

这个对于更有趣味或者更有创造性的尝试来说更为正确。如果你正在写你的第一个游戏:不要写成一个游戏引擎。类似的,不要在写好一个应用之前就去写一个框架。第一次的时候尽管大胆的去写一堆乱七八糟的代码。你是不会知道怎样拆分成模块的,除非你是先知。

单一库有类似的取舍:你事先不会知道怎样拆分你的代码,而一个大的错误显然比20个紧密关联的错误更容易处理。

当你知道哪些代码将会被舍弃、删除,或者替换的时候,你就可以采用更多的捷径。特别是当你要写一个一次性的客户端网站,或关于一个活动的网页的时候。或者任何一个有模版、要删除复本、要填补框架所留下的缺口的地方。

我不是说你应该重复同一件事情十次来纠正错误。引用 Perlis 的话:「所有东西都应该从上到下建立,除了第一次的时候。」你应该在每一次尝试时都去犯新的错误,接纳新的风险,然后通过迭代慢慢的来完善。

成为一个专业的软件开发者的过程就是不断积累后悔和错误清单的过程。你从成功身上学不到任何东西。并不是你能知道好的代码是什么样的,而是你对坏的代码记忆犹新。

项目不管怎样最终都会失败或者成为遗留代码。失败比成功更频繁。写十个大的泥球,看它们能将你带向哪比尝试去给一个粪球抛光更快速。

一次行删掉所有的代码比一段一段的去删更容易。

阶段6:把你的代码拆分成小块

大段的代码是最容易写的,但同时维护起来也最为昂贵。一个看起来很简单的修改就会以特定的方式影响代码库的几乎每个部分。本来作为一个整体删除起来很简单的东西,现在变得不可能去一段一段地删除了。

就像我们根据相互独立的任务来将我们的代码分层一样,从特定平台的代码到特定领域的代码,我们同样需要找到一种方法来梳理出顶层逻辑。


从一系列很困难的或者很容易变的设计决定开始。然后去设计一个个模块,让每一个模块都能隐藏一个设计上的决定,使其对其他决定不可见。 D. Parnas


我们根据代码之间没有共享的部分来拆分代码,而不是将其拆分成有共同功能的模块。我们把写起来、维护起来,或者删除起来最让人沮丧的部分互相隔离开。

我们构建模块不是为了复用,而是为了易于修改。

不幸的是,有些问题相比其他的问题而言分割起来更加困难和复杂。虽然单一责任原则说「每一个模块都应该只去解决一个难题」,但更重要的是「每一个难题都只应该由一个模块去解决」。

当一个模块做两件事情的时候,通常都是因为改变一部分需要另外一部分的改变。一个写得很糟糕但是有着简单接口的组件,通常比需要互相协调的两个组件更容易使用。


我如今再也不会尝试用「松耦合」这种速记一样的描述来定义那种应该被认可与接受的材料了,或许我永远不可能以清晰易懂的方式来定义它。但是当我看到它的时候我能够认出来,而当前的代码不属于那种。 SCOTUS Justice Stewart


你如果可以在一个系统中删除某一模块而不用因此去重写其他模块的话,这个系统就通常被称为是松耦合的。但是解释松耦合是什么样的比在一开始就建立一个这样的系统要容易多了。

甚至于写死一个变量 一次,或者使用命令行标记一个变量都可以叫松耦合。松耦合能让你在改变想法的同时不需要改写太多的代码。

比如,微软 Windows 的内部 API 和外部 API 就是因为这个目的而存在的。外部 API 与桌面程序的生命周期捆绑在一起,内部 API 则和内核捆绑在一起。隐藏这些 API 在给了微软灵活性的同时又不会挂掉过多的软件。

HTTP 中也有松耦合的例子:在你的 HTTP 服务器前设置一个缓存。将图片移到 CDN 上,仅改变一下到它们的链接。这两者都不会挂掉你的浏览器。

HTTP 的错误码是另外一个关于松耦合的例子:服务器之间常见的问题都有自己独特的错误码。当你收到400的时候,再尝试一次还是会得到同样的结果。如果是500则可能会变。结果是,HTTP客户端可以替代程序员处理许多的错误。

当把一个软件分解成更小的部分时,必须要考虑到如何去处理错误。这件事说比做容易。


我勉强决定去使用LATEX。在有错误存在的情况下去实现可靠的分布式系统。 Armstrong, 2003


Erlang/OTP 在处理错误方面有独到之处:监督树(supervision trees)。大致来说,每一个 Erlang 进程都由一个监督进程发起并监视。当一个进程遇到了问题的时候,它就会退出。当进程退出的时候,其监督进程会将其重启。

(这些监督进程由一个引导进程(bootstrap process)发起,当监督进程遇到错误的时候,引导进程会将其重启)

其思想是,快速的失败然后重启比去处理错误要快。像这样的错误处理看起来跟直觉相反 —— 当错误发生的时候通过放弃处理来获得可靠性。但是重启是解决暂时性错误的灵丹妙药。

错误处理和恢复最好是在代码的外层进行。这被称为端对端(end-to-end)原则。端对端原则说在一个连接的远端处理错误比在中间处理要更容易。即使在中间层进行处理,最终顶层的检查也无法被省去。如果不管怎样都需要在顶层来处理错误,那么为什么还要在里层去处理它们呢?

错误处理是一个系统可以紧密结合在一起的方式之一。除此之外还有许多其他紧耦合(tight coupling)的例子,但是要找一个糟糕的设计出来有一点不公平。除了 IMAP。

IMAP 中的每一个操作都像雪花一样,都有自己独特的选择和处理。错误处理相当痛苦:错误可能因为其他操作产生的结果而半路杀出。

IMAP 使用独特的令牌,而不是 UUID,来识别每一条信息。这些令牌也可能因为一个操作而中途被改变。许多操作都不是原子操作。找到一种可靠的方式将一封email从一个文件夹移动到另一个文件夹花费了25年时间。它还采用了一种特别的 UTF-7 编码,和一种独特的 base64 编码。

以上这些都不是我编的。

相比而言,文件系统和数据库是远程储存中好得多的例子。在文件系统中,操作的种类是固定的,但是却有很多可操作的对象。

虽然 SQL 像是一个比文件系统要广得多的接口,它仍然遵循相同的模式。若干对 set 的操作,许许多多对行的操作。虽然不能总是用一个数据库去替换出另一个数据库,但是找到可以和 SQL 一起使用的东西比找到任何一种自制的查询语言都更容易。

其他松耦合的例子有具备中间件、过滤器(filter)和管道(pipeline)的系统。例如,Twitter Finagle 的服务都是使用共同的 API,这使得泛型的超时处理、重试机制,和身份验证都能被毫不费力的加进客户端和服务器端的代码中。

(我很确定如果我不在这提UNIX管道的话,肯定会有人向我抱怨)

首先我们将我们的代码分层,但现在其中的一些层要共享一个接口:一系列有着不同实现的相同行为和操作。好的松耦合通常就意味着一致的接口。

一个健康的代码库不一定要完美的呈现出模块化。模块化的部分使写代码变得很有趣,就像乐高玩具的趣味来自于它所有的零件都可以被拼在一起一样。一个健康的代码库会有一些赘言和冗余,但它们使得可移植的组件间的距离恰到好处,因此你不会把自己套在里面。

松耦合的代码不一定就是易于删除的代码,但是它们替代和修改起来都会容易得多。

阶段7:持续的写代码

如果在写新代码的时候不需要去考虑旧有的代码,那么测试新的想法就要容易很多。并不是说一定要写小的模块,避免庞大的程序,而是说你的系统在你正常开发的同时还需要能够支持一两个试验。

功能发布控制(feature flag)是能让你在以后改变主意的一种方法。虽然 feature flag 被视作一种测试不同功能的方法,但同时它能让你在不重新部署的情况下就应用修改。

Google Chrome 是一个很好的例子,能说明其带来的好处。他们发现维持固定发布周期最困难的就是要合并一个长期存在的功能分支的时候。

能够在不需要重新编译的情况下激活和关闭新的代码,大的修改就可以在不影响现存代码的情况下被分解为更小的合并。如果新功能在代码库中更早出现的话,当一个长期的功能开发影响到其他部分的时候就会表现得更加明显。

Feature flag 并不是命令行开关,它是一种分离功能发布与合并分支,分离功能发布与代码部署的方式。当软件更新需要花费数小时、数天、甚至数周的时候,能够在运行中改变功能就变得越来越重要了。随便问一个运维人员,你就会知道任何一个可能在半夜把你叫起来的系统都值得在运行时去控制。

你更多的是要有一个反馈回路,而不是不停的迭代。模块更多的是用来隔离不同组件以应对改变的,而不仅是用来做代码复用的。处理代码的更改不仅仅是开发新的功能,同时也是抛弃掉旧的功能。写具有扩展性的代码是寄希望于三个月后你能把所有事情都做对。写可以被删除的代码则是基于相反的假设。

我在上文中谈到的策略 —— 分层、隔离、共同的接口、构造 —— 并不是有关写出优秀的软件的,而是关于怎样开发一个可以随着时间而改变的软件。


因此,管理上的问题不是要不要建一个试验性的系统然后把它抛弃掉。你会这么做的。[……]所以做好抛弃它的打算吧;无论如何你都会的。 Fred Brooks


你不必要将它全部抛弃,但是你需要删除某些部分。好的代码并不是要第一次就做对一件事。好的代码是那些不会造成障碍的遗留代码(legacy code)。

好的代码总是易于删除的代码。

« 较早的 文章 较新的 文章 »

Copyright © 2024 优大网 浙ICP备13002865号

回到顶部 ↑