如何在遗留代码零基础如何写代码上开发

购买纸质版 Growth:《全栈应用开发:精益实践》

阅读过程中遇到语法错误、拼写错误、技术错误等等,不妨来个Pull Request,这样可以帮助到其他阅读这本电子书的童鞋。

黄峰达(Phodal Huang)是一个创客、工程师、咨询师和作家。他毕业于西安文理学院电子信息工程专业,现作为一个咨询师就职于 ThoughtWorks 深圳。长期活跃于开源软件社区 GitHub,目前专注于物联网和前端领域。

作为一个技术作者,著有《自己动手设计物联网》(电子工业出版社)、《全栈应用开发:精益实践》(电子工业出版社,正在出版)。并在 GitHub 上开源有《Growth: 全栈增长工程师指南》、《GitHub 漫游指南》等七本电子书。

他热爱编程、写作、设计、旅行、hacking,你可以从他的个人网站: 了解到更多的内容。

当前为预览版,在使用的过程中遇到任何问题请及时与我联系。阅读过程中的问题,不妨在GitHub上提出来:

阅读过程中遇到语法错误、拼写错误、技术错误等等,不妨来个Pull Request,这样可以帮助到其他阅读这本电子书的童鞋。

支持作者,可以加入作者的小密圈:

这是一本不止于全栈工程师的学习手册,它也包含了如何成为一个 Growth Hacker 的知识。

谨以此文献给每一个为成为优秀全栈工程师奋斗的人。

技术在过去的几十年里进步很快,也将在未来的几十年里发展得更快。今天技术的门槛下降得越来越快,原本需要一个团队做出来的 Web 应用,现在只需要一两个人就可以了。

同时,由于公司组织结构的变迁,以及到变化的适应度,也决定了赋予每个人的职责将会越来越多。尽管我们看到工厂化生产带来的优势,但是我们也看到了精益思想带来的变革。正是这种变革让越来越多的专家走向全栈,让组织内部有更好的交流。

你还将看到专家和全栈的两种不同的学习模式,以及全栈工程师的未来。

从开始的 CGI 到 MVC 模式,再到前后端分离的架构模式,都在不断地降低技术的门槛。而这些门槛的降低,已经足以让一两个人来完成大部分的工作了。

二十年前的网站以静态的形式出现,这样的网站并不需要太多的人去维护、管理。接着,人们发明了 CGI (通用网关接口,英语:Common Gateway Interface)来实现动态的网站。下图是一个早期网站的架构图:

当时这种网站的URL类似于:

(PS:这个链接是为了讲解而存在的,并没有真实存在。)

用户访问上面的网页的时候就会访问,cgi-bin 的路径下对应的 getblog 脚本。你可以用 Shell 返回这个网页:

Blabla,各种代码混乱地夹杂在一起。不得不说一句:这样的代码在2012年,我也看了有一些。简单地来说,这个时代的代码结构就是这样的:

这简直就是一场恶梦。不过,在今天好似那些 PHP 新手也是这样写代码的。

好了,这时候我们就可以讨论讨论 MVC 模式了。

我有理由相信 Martin Fowler 的《企业应用架构模式》在当时一定非常受欢迎。代码从上面的耦合状态变成了:

相似大家也已经对这样的架构很熟悉了,我们就不多解释了。如果你还不是非常了解的话,可以看看这本书后面的部分。

后台服务化与前端一致化架构

在今天看来,我们可以看到如下图所示的架构:

后台服务化与前台一致化架构

后台在不知不觉中已经被服务化了,即只提供API接口和服务。前端在这时已经尽量地和 APP 端在结合,使得他们可以保持一致。

软件开发的核心难题:沟通

软件开发在过去的几十年里都是大公司的专利,小公司根本没有足够的能力去做这样的事。在计算机发明后的几十年里,开发软件是大公司才能做得起的。一般的非技术公司无法定制自己的软件系统,只能去购买现有的软件。而随着技术成本的下降,到了今天一般的小公司也可以雇佣一两个人来做同样的事。这样的演进过程还真是有意思:

在这其中的每一个过程实质上都是为了解决沟通的问题。从瀑布到敏捷是为了解决组织内沟通的问题,从敏捷到精益不仅仅优化了组织内的沟通问题,还强化了与外部的关系。换句话说,精益结合了一部分的互联网思维。

在最开始的时候,我们预先设计好我们的功能,然后编码,在适当的时候发布我们的软件:

然而这种开发方式很难应对市场的变化——当我们花费了几年的时间开发出了一个软件,而这个软件是几年前人们才需要的。同时,由于软件开发本身的复杂度的限制,复制的系统在后期需要大量的系统集成工作。这样的集成工作可能要花费上大量的时间——几星期、几个月。

当人们意识到这个问题的时候,开始改进工作流程。出现了敏捷软件开发,这可以解释为什么产品经理会经常改需求。如果一个功能本身是没必要出现的话,那么为什么要花功夫去开发。但是如果一个功能在设计的初期就没有好好设计,那么改需求也是必然的。

现有的互联网公司的工作流程和敏捷软件开发在很多部分上是相似的,都有迭代、分析等等的过程:

但是据我的所知:国内的多数互联网公司是不写测试的、没有 Code Review 等等。当然,这也不是一篇关于如何实践敏捷的文章。敏捷与瀑布式开发在很大的区别就是:沟通问题。传统的软件开发在调研完毕后就是分析、开发等等。而敏捷开发则会强调这个过程中的沟通问题:

敏捷软件开发的沟通模型

在整个过程中都不断地强调沟通问题,然而这时还存在一个问题:组织结构本身的问题。这样的组织结构,如下图所示:

如果市场部门/产品经理没有与研发团队坐一起来分析问题,那么问题就多了。当一个需求在实现的过程中遇到问题,到底是哪个部门的问题?

同样的如果我们的研发部门是这样子的结构:

那么在研发、上线的过程中仍然会遇到各种的沟通问题。

现在,让我们回过头来看看大公司的专家与小公司的全栈。

大公司的专家与小公司的全栈

如果你经常看一些关于全栈和专家的技术文章的时候,你就会发现不同的人在强调不同的方向。大公司的文章喜欢强调成为某个领域的专家,小公司喜欢小而美的团队——全栈工程师。

如我们所见的:大公司和小公司都在解决不同类型的问题。大公司要解决性能问题,小公司要活下去需要依赖于近乎全能的人。并且,大公司和小公司都在加班。如果从这种意义上来说,我们可以发现其实大公司是在剥削劳动力。

我们所见到的那些关于技术人员应该成为专家的文章,多数是已经成为某个技术领域里的专家写的文章。并且我们可以发现很有意思的一点是:他们都是管理者。管理者出于招聘的动机,因此更需要细分领域的专家来帮助他们解决问题。

相似的,我们所看到的那些关于成为全栈工程师的文章,多数是初创公司的 CTO 写的。而这些初创公司的 CTO 也多数是全栈工程师,他们需要招聘全栈工程师来帮助他们解决问题。

而不知你是否也注意到一点:专家们也在强调“一专多长”。因为单纯依靠于一个领域的技术而存在的专家已经很少了,技术专家们不得不依据于公司的需求去开拓不同的领域。毕竟“公司是指全部资本由股东出资构成,以营利为目的而依法设立的一种企业组织形式;”,管理人们假设技术本身是相通的,既然你在技术领域里有相当高的长板,那么进入一个新的技术也不是一件难事。

作为一个技术人员,我们是这个领域中的某个子领域专家。而作为这样一个专家,我们要扩展向另外一个领域的学习也不是一件很难的事。借鉴于我们先前的学习经验,我们可以很快的掌握这个新子域的知识。如我们所见,我们可以很快地补齐图中的短板:

在近来的探索中发现有一点非常有意思:如果依赖于20/80法则的话,那么成为专家和全栈的学习时间是相当的。在最开始的时候,我们要在我们的全栈工程和专家都在某个技术领域达到80分的水平。

那么专家,还需要80%的时间去深入这个技术领域。而全栈工程师,则可以依赖于这80%的时候去开拓四个新的领域:

尽管理论上是如此,但是专家存在跨领域的学习障碍——套用现有模式。而全栈也存在学习障碍——如何成为专家,但是懂得如何学习新的领域。

解决问题的思路:不同的方式

有意思的是——成为专家还是成为全栈,取决于人的天性,这也是两种不同的性格决定的。成为管理者还是技术人员看上去就像一种简单的划分,而在技术人员里成为专家还是全栈就是另外一种划分。这取决于人们对于一个问题的思考方式:这件事情是借由外部来解决,还是由内部解决。下面这张图刚好可以表达我的想法:

而这种思维依据于不同的事情可能会发生一些差异,但是总体上来说是相似的。当遇到一个需要创轮子的问题时,我们就会看到两种不同的方式。

对于全栈工程师来说,他们喜欢依赖于外部的思维,用于产生颠覆式思维。如 AngularJS 这样的框架便是例子,前端结合后端开发语言 Java 的思维而产生。而专家则依赖于内部的条件,创造出不一样的适应式创新。如之前流行的 Backbone 框架,适应当时的情况而产生。

全栈工程师的未来:无栈

全栈工程师本身不应该仅仅局限于前端和后台的开发,而可以尝试去开拓更广泛的领域——因为全栈本身是依赖于工程师本身的学习能力,正是这种优秀的学习能力可以让他们接触更广泛的知识。

如果你也尝试过面试过全栈工程师,你会怎么去面试他们呢?把你知道的所有的不同领域的问题都拿出来问一遍。是的,这就是那些招聘全栈工程师的公司会问你的问题。

人们以为全栈工程师什么都会,这是一个明显的误区——然而要改变这个误区很难。最后,导致的结果是大家觉得全栈工程师的水平也就那样。换句来说,人们根本不知道什么是全栈工程师。在平时的工作里,你的队伍都知道你在不同领域有丰富的知识。而在那些不了解你的人的印象里,就是猜测你什么都会。

因此,这就会变成一个骂名,也是一个在目前看来很难改变的问题。在这方面只能尽可能地去了解一些通用的问题,并不能去了解所有的问题。在一次被面试全栈工程师的过程中,有一个面试官准备了几个不同语言(JavaScript、Java、Python、Ruby)的问题来问我,我只想说 Ciao —— 意大利语:你好!

除了这个问题——人们不了解什么是全栈工程师。还有一个问题,就是刚才我们说的成为专家的老大难问题。

让我毫不犹豫地选择当全栈工程师有两个原因:

  1. 这个世界充满了未解的迷,但是我只想解开我感兴趣的部分。
  2. 没有探索,哪来的真爱?你都没有探索过世界,你就说这是你最喜欢的领域。

当我第一次看到全栈工程师这个名字的时候,我发现我已然是一个全栈工程师。因为我的学习路线比较独特:

  • 中小学,因为比赛,开始学习编程
  • 高中,因为兴趣,开始学习操作系统内核设计以及游戏编程
  • 大学,因为专业走向硬件的道路,因为生活拮据,用Web开发来赚钱
  • 工作:主要工作领域便是:Java后端、前后端分离、SPA前端,业余还写写APP、游戏、写作

而在当时我对 SEO 非常感兴趣,我发现这分析和 Marketing 似乎做得还可以。然后便往 Growth Hacking 发展了:

而这就是全栈学习带来的优势,学过的东西多,学习能力就变强。学习能力往上提的同时,你就更容易进入一个新的领域。

  • 《精益企业: 高效能组织如何规模化创新》

在我们第一次开始写程序的时候,都是以 Hello World 开始的。或者:

过去的十几年里,试过用二十几种不同的语言,每个都是以 hello,world 作为开头。在一些特定的软件,如 Nginx,则是 It Works

这是一个很长的故事,这个程序最早出现于1972年,由贝尔实验室成员布莱恩·柯林汉撰写的内部技术文件《A Tutorial Introduction to the Language B》之中。不久,同作者于1974年所撰写的《Programming in C: A Tutorial》,也延用这个范例;而以本文件扩编改写的《C语言程序设计》也保留了这个范例程式。工作时,我们也会使用类似于

同时需要注意的一点是,在每个大的项目开始之前我们应该去找寻好开发环境。搭建环境是一件非常重要的事,它决定了你能不能更好地工作。毕竟环境是生产率的一部分。高效的程序员和低效程序员间的十倍差距,至少有三倍是因为环境差异。

因此在这一章里,我们将讲述几件事情:

  1. 如何搭建相应操作系统上的环境

一个好的工具确实有助于编程,但是他只会给我们带来的是帮助。我们写出来的代码还是和我们的水平保持着一致的。

什么是好的工具,这个说法就有很多了,但是有时候我们往往沉迷于事物的表面。有些时候 Vim 会比 Visual Studio 强大,当你只需要修改的是一个配置文件的时候,简单且足够快捷——在我们还未用 VS 打开的时候,我们已经用 Vim 做完这个活了。

“好的装备确实能带来一些帮助,但事实是,你的演奏水平是由你自己的手指决定的。” – 《REWORK》

作为一个 IDE 有时候忽略的因素会过多,一开始的代码由类似于 Sublime text 之类的编辑器开始会比较合适。于是我们又开始陷入 IDE 及 Editor 之战了,无聊的时候讨论一下这些东西是有点益处的。相互了解一下各自的优点,也是不错的,偶尔可以换个环境试试。

刚开始学习的时候,我们只需要普通的工具,或者我们习惯了的工具去开始我们的工作。我们要的是把主要精力放在学习的东西上,而不是工具。刚开始学习一种新的语言的时候,我们不需要去讨论哪个是最好的开发工具,如 Java,有时候可能是 Eclipse,有时候可能是 Vim,如果我们为的只是去写一个 hello,world。在 Eclipse 上浪费太多的时间是不可取的,因为他用起来的效率可不比你在键盘上敲打来得快,当你移动你的手指去动你的鼠标的时候,我想你可以用那短短的时间完成编译,运行了。

寻找工具的目的和寻找捷径是一样的,我们需要更快更有效率地完成我们的工作,换句话说,我们为了获取更多的时间用于其他的事情。而这个工具的用途是要看具体的事物的,如果我们去写一个小说、博客的时候,word 或者 web editor 会比 tex studio 来得快,不是么。我们用 TEX 来排版的时候会比我们用 WORD 排版的时候来得更快,所以这个工具是相对而论的。有时候用一个顺手的工具会好很多,但是不一定会是事半功倍的。我们应该将我们的目标专注于我们的内容,而不是我们的工具上。

我们用 Windows 自带的画图就可以完成裁剪的时候,我们就没必要运行起 GIMP 或者 Photoshop 去完成这个简单的任务。效率在某些时候的重要性,会比你选择的工具有用得多,学习的开始就是要去了解那些大众推崇的东西。

Windows 的功能很强大,只是大部分人用的是只是小小一部分。而不是一小部分,即使我们天天用着,我们也没有学习到什么新的东西。和这个就如同我们的工具一样,我们天天用着他们,如果我们只用 Word 来写写东西,那么我们可以用 Abiword 来替换他。但是明显不太可能,因为强大的工具对于我们来说有些更大的吸引力。

如果你负担得起你手上的工具的话,那么就尽可能去了解他能干什么。即使他是一些无关仅要的功能,比如 Emacs 的煮咖啡。有一本手册是最好不过的,手册在手边可以即时查阅,不过出于环保的情况下,就不是这样子的。手册没有办法即时同你的软件一样更新,电子版的更新会比你手上用的那个手册更新得更快。

Linux 下面的命令有一大堆,只是我们常用的只有一小部分——20%的命令能够完成80%的工作。如同 CISC 和 RISC 一样,我们所常用的指令会让我们忘却那些不常用的指令。而那些是最实用的,如同我们日常工作中使用的 Linux 一样,记忆过多的不实用的东西,不比把他们记在笔记上实在。我们只需要了解有那些功能,如何去用他。

越来越多的框架和语言出现、更新得越来越快。特别是这样一个高速发展的产业,每天都在涌现新的名词。如同我们选择语言一样,选择合适的有时候会比选得顺手的来得重要。然而,这个可以不断地被推翻。

当我们熟悉用 Python、Ruby、PHP 等去构建一个网站的时候,JavaScript 用来做网站后台,这怎么可能——于是

我们就会看到下面的响应过程:

以“*”开始的前8行是一些连接相关的信息,称为响应首部。我们向域名 发出了请求,接着 DNS服务器告诉了我们网站服务器的 IP,即', true);

我们只需要简单的创建一个请求对象实例,打开一个 URL,然后发送这个请求。当传输完毕后,结果的 HTTP 状态以及返回的响应内容也可以从请求对象中获取。

而这个返回的内容可以是多种格式,如 XML 和 JSON,但是从近年的趋势来看,XML 基本上已经很少看到了。这里我们以 JSON 为主,来简单地介绍一下返回数据的解析。

成为理想的数据交换语言。易于人阅读和编写,同时也易于机器解析和生成(一般用于提升网络传输速率)。

JSON 格式的数据具有以下的一些特点:

如下所示的是一个简单的对比过程:

如果我们要取出上面数值中的age,那么我们只需要这样做:

同样的,对于 XML 来说,我们有下面的格式:

而如果我们要取出上面数据中的age的值,他将是这样的:

对比一下,我们可以发现XML的数据不仅仅解析上比较麻烦,而且还繁琐。

在人们大规模地开始 Web 应用的时候,我们在授权的时候遇到了一些问题,而这些问题不是 Cookie 所能解决的。Cookie 存在一些明显的问题:不支持跨域、并且不是无状态的、不能使用CDN、与系统耦合等等。除了解决上面的问题,它还可以提高性能等等。基于 Session 的授权机制需要服务端来保存这个状态,而使用 JWT 则可以跳过这个问题,并且使我们设计出来的 API 满足 RESTful 规范。即,我们 API 的状态应该是没有状态的。因此人们提出了 JWT 来解决这一系列的问题。

通过 JWT 我们可以更方便地写出适用于前端应用的认证方案,如登陆、注册这些功能。当我们使用 JWT 来实现我们的注册、登陆功能时,我们在登陆的时候将向服务器发送用户名和密码,服务器验证后将生成对应的 Token。在下次我们进行页面操作的时候,如访问 /Dashboard 时,发出的 HTTP 请求的 Header 中会包含这个 Token。服务器在接收到请求后,将对这个 Token 进行验证并判断这个 Token 是否已经过期了。

需要注意的一点是:在使用 JWT 的时候也需要注意安全问题,在允许的情况下应该使用 HTTPS 协议。

在一些网站上为了实现推送技术,都采用了轮询的技术。即在特定的时间间隔里,由浏览器向服务器发出 HTTP 请求,然后浏览器便可以从服务器获取最新的消息。如下图所示的是 Google Chrome 申请开发者账号时发出的对应的请求:

从上图中我们可以看到,Chrome 的前台正在不断地向后台查询 API 的结果。由于浏览器需要不断的向服务器发出请求,而 HTTP 的 Header 是非常长的,即使是一个很小的数据也会占用大量的带宽和服务器资源。为了解决这个问题,HTML5 推出了一种在单个 TCP 连接上进行全双工通讯的协议WebSocket。

WebSocket 可以让客户端和服务器之间存在持久的连接,而且双方都可以随时开始发送数据。

在我们真正开始去写代码之前,我们可能会去考虑一些事情。怎么去规划我们的任务,如何去细分这个任务。

  1. 如果一件事可以自动化,那么就尽量去自动化,毕竟你是一个程序员。
  2. 快捷键!快捷键!快捷键!
  3. 使用可以帮助你快速工作的工具——如启动器。

不过不得不提到的一点是:你需要去考虑这个需求是不是一个坑的问题。如果这是一个坑,那么你应该尽早的去反馈这个问题。沟通越早,成本越低。

整个编程的过程如下图所示:

  1. Kick Off。在这个步骤中,我们要详细地了解我们所需要做的东西、我们的验收条件是什么、我们需要做哪些事情。
  2. Tasking。简单的规则一下,我们需要怎么做。一般来说,如果是结对编程的话,还会记录下来。
  3. 最新的代码。对于使用 Git 来管理项目的团队来说,在一个任务刚开始的时候应该保证本地的代码是最新的。
  4. Test First。测试优先是一个很不错的实践,可以保证我们写的代码的健壮,并且函数尽可能小,当然也会有测试。
  5. Code。就是实现功能,一般人都知道。
  6. 重构。在我们实现了上面两步之后,我们还需要重构代码,使我们的代码更容易阅读、更易懂等等。
  7. 提交代码。这里的提交代码只是本地的提交代码,因此都提倡在本地多次提交代码。
  8. 运行测试。当我们完成我们的任务后,我们就可以准备 PUSH 代码了。在这时,我们需要在本地运行测试——以保证我们不破坏别人的功能。
  9. 等 CI 测试通过。如果这时候 CI 是挂的话,那么我们就需要再修 CI。这时其他的人就没有理由 PUSH 代码,如果他们的代码也是有问题的,这只会使情况变得愈加复杂。

不过,在最开始的时候我们要了解一下如何去搭建一个项目。

Web 应用的构建系统

构建系统(build system)是用来从源代码生成用户可以使用的目标的自动化工具。目标可以包括库、可执行文件、或者生成的脚本等等。

对比于 Web 应用开发来说,构建系统应该还包括应用打包(如 Java 中的 Jar 包,或者用于部署的 RPM 包)、源代码分析、测试覆盖率分析等等。

Web 应用的构建过程

在刚创建项目的时候,我们都会有一个完整的构建思路。如下图便是这样的一个例子:

这是一个后台语言用的是 Java,前台语言用的是 JavaScript 项目的构建流程。

Compile。对于那些不是用浏览器的前端项目来说,如 ES6、CoffeeScript,他们还需要将代码编译成浏览器使用的 JavaScript 版本。对于 Java 语言来说,他需要一个编译的过程,在这个编译的过程中,会检查一些语法问题。

Check Style。通常我们会在我们的项目里定义一些代码规范,如 JavaScript 中的使用两个空格的缩进,Java 的 Checkstyle 中一个函数不能超过30行的限制。

单元测试。作为测试中最基础也是最快的测试,这个测试将集中于测试单个函数的是不是正确的。

功能测试。功能测试的意义在于,保证一个功能依赖的几个函数组合在一起也是可以工作的。

Mock Server。当我们的代码依赖于第三方服务的时候,我们就需要一个 Mock Server 来保证我们的功能代码可以独立地测试。

集成测试。这一步将集成前台、后台,并且运行起最后将上线的应用。接着依据于用户所需要的功能来编写相应的测试,来保证一个个的功能是可以工作的。

打包。对于部署来说,直接安装一个 RPM 包,或者 DEB 包是最方便的事。在这个包里会包含应用程序所需的所有二进制文件、数据和配置文件等等。

上传包。在完成打包后,我们就可以上传这个软件包了。

部署。最后,我们就可以在我们的线上环境中安装这个软件包。

Web 应用的构建实战

下面就让我们来构建一个简单的 Web 应用,来实践一下这个过程。在这里,我们要使用到的一个工具是 Gulp,当然对于 Grunt 也是类似的。

可以直接得到一个不错的结论是我的博客的主要流量来源是搜索引擎,再细细一看数据:

另外,也许不太明显的方式,建立链接(或者至少流量)是使用社交媒体 - 所以设置你的 Facebook ,Twitter 和谷歌,每当你有新的链接一定要分享。这些通道也可以作为一个有效的渠道,推动更多的流量到您的网站。

由社交渠道带来的流量在现在已经越来越重要了,对于一些以内容为主导而且处于发展初期的网站,可以迅速带来流量。一些更简单的办法就是交换链接,总之这个话题有些沉重,可能会带来一些负面的影响,如黑帽 SEO。。。。

用户体验设计(英语:User Experience Design),是以用户为中心的一种设计手段,以用户需求为目标而进行的设计。设计过程注重以用户为中心,用户体验的概念从开发的最早期就开始进入整个流程,并贯穿始终。其目的就是保证:

  • 对用户体验有正确的预估
  • 认识用户的真实期望和目的
  • 在核心功能还能够以低廉成本加以修改的时候对设计进行修正
  • 保证核心功能同人机界面之间的协调工作,减少 BUG。

关于 UX 的定义我觉得在知乎上的回答似乎太简单了,于是在网上寻寻觅觅终于找到了一个比较接近于答案的回答。原文是在: ,这又是一篇不是翻译的翻译。

用户体验设计(英语:User Experience Design),是以用户为中心的一种设计手段,以用户需求为目标而进行的设计。设计过程注重以用户为中心,用户体验的概念从开发的最早期就开始进入整个流程,并贯穿始终。其目的就是保证:

  • 对用户体验有正确的预估
  • 认识用户的真实期望和目的
  • 在核心功能还能够以低廉成本加以修改的时候对设计进行修正
  • 保证核心功能同人机界面之间的协调工作,减少BUG。

从下图中我们可以看到一些 UX 所需要的知识体系:

  • 声音设计 (网页设计中比较少)
  • 内容 (文字,视频,声音)

交互设计便是用户体验设计的重点。我们再来看看另外的这张图片

一个好的软件应该是简单的,并且是令人愉快的。

在不同的 UX 书籍里,似乎就会说到【简约至上】。简单就是“单纯清楚、不复杂”。而这里的简单并不仅仅只是不复杂那么简单。对于一个软件来说,简单实际上是你一下子就能找到你想要的东西,如:

而我们并不可能在一开始就得到这样的结果,这需要一个复杂的过程。而在这个过程开始之前,我们一定要知道的一点是:我们的用户到底需要什么?

如果我们无法知道这一点,而只是一直在假想客户需要什么,那么这会变成一条死路。

接着在找寻的过程中,发现了一个有意思的图,即精益画布:

首先,我们需要知道几个用户而存在的问题——即客户最需要解决的三个问题。并且针对三个问题提出对应的解决方案,这也就是产品的主要功能。

那么,这两点结合起来对于用户来说就是简单——这个软件能解决客户存在的主要问题。

如果我们可以完成这部分功能的话,那么这就算得上是一个有用的软件。

而实际上“实用”则是位于用户体验的最底层,如下图所示:

这时候就需要尽量往可用靠拢。怎样对两者进行一个简单的划分?

要达到可用的级别,并不是一件困难的事:遵循现有软件的模式

换句话说,这时候你需要的是一本 Cookbook。这本 Cookbook 上面就会列出诸多现有的设计模式,只需要参考便使用就差不多了。

同样的,我便尝试用《移动应用 UI 设计模式》一本书对我原有的软件进行了一些设计,发现它真的可以达到这样的地步。

而这也类似于在编程中的设计模式,遵循模式可以创造出不错的软件。

尽管对于这方面没有非常好的认识,但是还是让我们来看看我们现在可以到哪种程度。如在一开始所说的,我们需要满足用户的需求,这就是我们的目标:

而在最上面的视觉设计则需要更专业的人来设计。

  • 《怦然心动——情感化设计交互指南》
  • 《移动应用UI设计模式》

第一次意识到这本书很有用的时候,是我在策划一个视频。第二次,则是我在计划写一本书的时候。

在《认知设计》一书中,提到了下面的学习体验,即“流” (Flow)。而在我们学习的过程中,我们也会有类似的学习过程。

如在早期我学习 Emacs 和 GNU/Linux 的时候,也曾经放弃过,虽然在当时我已经读过 Linux 内核。然而,在应用之前进行理论学习并没有卵用。

通常我们会有类似于下面的学习体验,对于一本书来说有下面的体验似乎也是一件很不错的事:

  1. 在最开始学习的时候,我们需要一点理论基础,以及我们需要学点什么。
  2. 然后,我们需要构建一个简单可用的系统,以获取信心。如果这一步没有想象中那么简单,那么我们可能会放弃学习。或者等到某个时期成熟的时刻,如在我开始学习《设计模式》的时候,那么本书的高度太高了。直到有一天,我了解到了一本叫《Head First 设计模式》的书,才重新把 GoF 的书看了一遍,发现其实也没有想象中的难。
  3. 接着在完成了某个功能之后,我可能继续学习某个理论,用于支撑下一步计划。
  4. 在那之后,我觉得这一步可能也不是那么难,因为已经有了前面的基础。如果某一步失败的时候,那么我们可能会继续寻找某些可靠的方案,又或者是理论支撑。
  5. 直到有一天,我们来到了一个瓶颈的前面,现有的方案已经不满足我们的需求。对于这个问题,我们可能已经没有一个更好的解决方案。于是,我们可能就需要创建一个轮子,只是在这时,我们不知道怎样去造轮子。
  6. 于是我们开始学习造轮子。

只有保持一个学习的过程,才会让我们在这一步步的计划中不会退缩,也不能退缩。

交付管道的建立和自动化是持续交付的基础

更关注代码质量。持续集成是为了确保随着需求变化而变化的代码,在实现功能的同时,质量不受影响。因此,在每一次构建后会运行单元测试,保证代码级的质量。单元测试会针对每一个特定的输入去判断和观察输出的结果,而单元测试的粒度则用来平衡持续集成的质量和速度。

持续集成的核心价值在于:

  1. 持续集成中的任何一个环节都是自动完成的,无需太多的人工干预,有利于减少重复过程以节省时间、费用和工作量;
  2. 持续集成保障了每个时间点上团队成员提交的代码是能成功集成的。换言之,任何时间点都能第一时间发现软件的集成问题,使任意时间发布可部署的软件成为了可能;
  3. 持续集成还能利于软件本身的发展趋势,这点在需求不明确或是频繁性变更的情景中尤其重要,持续集成的质量能帮助团队进行有效决策,同时建立团队对开发产品的信心。

在前面的内容里,我们已经介绍了持续集成的各项基础设施——如使用版本管理、编写测试、自动化部署。要构建这样的一个持续集成系统需要下面的内容:

我们已经实现了前两点,针对于第三点——持续集成服务器,我们可以以 Jenkins 为例做一些简单的说明。它是一种基于 Java 开发的持续集成工具,并提供了用于监控持续重复工作的软件平台。

它可以让整个开发流程到部署都实现自动化。由于每个功能可以一点点的加在 build 中,那么这样就能保证每次的新 build 可以交付新的功能。同时,我们可以根据用户的反馈情况及时调整开发方向,降低项目风险。

我们就可以对这个工作流展开深入介绍。持续集成主要就是要保证整个过程是可持续的。如下图是一个持续集成的工作流:

不同的开发者在自己的机器上开发功能代码。在完成一定的本地提交后,这些代码将会提交给源代码控制服务器。不过,在那之前我们应该在本地跑测试来减少持续集成失败的情况。接着,我们的CI会定时去获取源码服务器是否有代码修改。如果有代码修改,那么我们的集成服务器将会获取这些代码。然后,构建这个项目,运行测试,再输出返回结果。最后,我们可以开发一些小工具来提醒用户 CI 是否运行成功。

如果这个过程中 CI 运行失败的话,那么我们就不能再提交新的代码——除了修复 CI 的代码外。持续集成变红不能过夜,要么快速解决,要么回退。

在这个过程中,有两点值得注意,一个是小步前进,一个是迟早反馈。

小步前进是一系列步骤的集合,其目的是:集成越早问题越小。即当我们的代码越早的提交到源码服务器,那么其他人就可以尽可能早的和我们的代码集成到一起。这也意味着,每天结束时,我们在本地的修改要尽可能小,并且这些修改还要保证不会破坏持续集成。

我们需要频繁地在本地提交我们的代码,编写独立的测试——如果我们在最后才编写测试,就会拖慢整个流程,它使得我们不能尽可能早的提交代码。

反馈越早,那么问题越小。

无论是精益还是敏捷都在强调一点——尽早反馈,反馈对于提高敏捷开发流程效力非常重要。从日常的:

我们都在做尽可能小的反馈,这些实践对于我们完成一个好的持续集成来说是非常重要的基础。

-《持续交付:发布可靠软件的系统方法》

持续交付依赖于一系列的工具和实践,下图是一个持续交付的工作流:

还有一系列与开发无关的技能:

在我们使我们的项目可以持续交付软件包的时候,我们需要

在本地编写代码时,我们需要设置本地的开发环境。假设我们要开始一个 Java Web 项目,在我们的开发机器上,我们需要安装:

  • 版本管理工具,如 git,用于管理源代码。
  • 构建工具,如 gradle,用于安装依赖、运行测试、构建工程等等。
  • 语法检测工具,如 checkstyle,用于检查代码语法。
  • 单元测试框架,如 JUnit,用于进行单元测试。
  • 集成测试框架,如 Cucumber、Selenium,用于做行为测试。

除此,在我们的项目代码里,我们还需要:

  • CI运行脚本,用于在 CI 上运行指定的测试。
  • 上传包脚本,用于上传 build 完的软件包。
  • 部署脚本,用于在本地部署包到测试环境。
  • 监控代码,用于监测网站性能和用户行为。

当然我们还需要辅助一些测试工具来测试网站,如性能测试、网络测试等等。

为什么在这里会出现一个持续集成环境?我也不知道,只是想到了这里。由于我们需要持续集成,所以我们也需要一个运行持续集成服务器的机器。

持续集成服务器是由两部分组成的:Master 和 Agent。即一个用于控制其他运行持续集成服务的机器,以及执行指令的机器。因此,我们需要在一台机器上安装 Master 软件,在另外一台机器上作为 Agent。在我们的 Agent上,我们需要安装相对应的运行服务的软件,如

  • 指定版本的语言环境 ,如Java、Python。
  • 版本管理工具,及对应的密钥。
  • 打包工具,如 RPM。
  • 虚拟桌面,即可以模拟桌面浏览器的软件。

同时,我们还需要有一个地方放置我们的RPM包。

相比于上面两个环境来说,测试环境就比较简单了。我们只需要创建几个不同的环境,即开发者的测试环境、QA 环境、模拟线上环境,在这几个不同的环境上有不同的配置。

在持续交付之外的,还有持续部署——这个就更依赖于团队的组织结构了。其与持续交付的对比如下图所示:

我们可以从图中看到,两者的最大不同之处在于:持续部署会直接将构建生成的部署到产品环境。这就意味着,我们不仅要有强大的技术实力,也要有足够的组织支持才能做到。而这部分已经超出了软件开发的内容了~~。

如果说持续交付则是一种对卓越的追求,那么持续学习应该就是追求软件卓越。 如果说持续集成是一种软件开发实践,那么对于技术人员来说——持续写作应该就是持续学习的实践

生活总会遇到压力,来自工作上的也好,来自对于技术上的兴趣也罢,我们需要持续来断地学习。没有一直能立于不败的方法,在传说中的武林上也是如此。

对于持续学习来说,通常会有以下的

有意思的是持续学习有额外的好处便是

  • 持续学习可以降低危机感

看过如此多的金庸、古龙小说我们都会发现有那么多的人都在追求武功上的卓越,有的走火入魔了,有的铤而走险杀人放火,暂且不讨论这些。我们简单的以大部分的主角为例,大部分的主角自小就练得一手好武艺,少部分除外,而他们通过会比前辈厉害,只是因为我们看了前人的说,现在也是如此。

20 年前要建一个淘宝怕是没有两三个月十几个人是不行的,但是今天要建出原来淘宝的模样,也许一个人单枪匹马一两天就能搞定了,还能上线。

有意思的是武林小说的武林秘籍少之又少,正常情况下能学到的或许就是教科书上的种种。而现在,如果我们要学习 UX 的话,我们很容易可以从亚马逊上拿到一个书单,又或者是某个博客里面列举出来的:《用户体验要素》、《交互设计沉思录》、《怦然心动——情感化交互设计指南》等等。

我们可以更加方便快捷地获取我们所需要的知识从书上、网上等等。

阅读更多的书籍是持续学习的基础。

总会听到有些人在工作之余看了更多的书,在某种情况下来说是有意义的。我们需要不断地去阅读。

编程算是一个开发人员工作时一直在做的,而对于工作之后来说,到底还会有多少人继续编程就是一个有意思的问题。

对于一个有兴趣的程序员来说,工作和兴趣都是分开的,可以将工作视之为无味的东西,但是休息时间呢?可以用来创造自己觉得有意义的东西,可以用来认识更多志同道合的人,对于不满现状的人更是如此,或许为自己创造了更多的机会。

工作之后编程,不应该是为了工作而编程,应该为了兴趣而编程,或者其他。如果没有时间,是不是因为加班了,对于刚开始养家糊口来说加班是没有办法的,但是如果不是的话,又没时间,是不是……

对于一个技术人员来说,写作可能不是一件有意思的事,但是也不是一件很难的事,没有必要将大量的文字用文本表示。写给其他技术人员看的,有时候更多的是代码、思路、图。写作对于学习的意思怕是有一大把,写作是最好的输入,也是最好的输出。你需要为你的这篇文章

然而这些都是有价值的,你也许可以从中得到

对于我来说,更多的是对于读者SEO 的兴趣,SEO 是一门艺术。

尽管维基百科上对遗留系统的定义是:

一种旧的方法、旧的技术、旧的计算机系统或应用程序。

但是实际上,当你看到某个网站宣称用新的框架来替换旧的框架的时候,你应该知晓他们原有的系统是遗留系统。人们已经不想在上面工作了,很多代码也不知道是干什么的,也没有人想去深究——毕竟不是自己的代码。判断是否是遗留代码的条件很简单,维护成本是否比开发成本高很多。

  • 没有文档或者不够详细、看不懂

在维护这一类系统的过程中,我们可能会遇到一些原因来修改代码。如《修改代码的艺术》的一书中所说,修改软件有四大原因:

当我们修改代码之后,我们将继续引进新的 Bug。

与我们生活息息相关的很多软件里满是错误、脆弱的代码,并且难以扩展。这就是我们说的“遗留代码”。

相信你也经常看到某某网站的架构之路,会发现其中一个很有趣的过程就是他们会把之前的架构抛弃掉。接着,他们又做了一个这样的系统,然后过些年这个系统又被重做了。究其原因,会发现这个架构是在几年前设计的。在几年前,他是非常好的架构。但是随着时间的演变,他还是几年前的架构。这是为什么呢?

没有自动化测试的代码就是遗留代码,不管它是十年前写的,还是昨天写的。

从一个新手程序员到一个老鸟,我们的编程水平都在不断增加。但是我们过去写的代码一直都在那里,我们一直都没有足够的勇气去动它们。因为我们知道如果一不小心改错了什么,就会导致一些意外的 Bug。这些 Bug 可能会对我们的编程生涯造成一些影响。

我们不知道这样做的后果,是因为我们没有对原来的代码进行测试。如果代码都是经过测试的,那么我们在修改中出的错,都会在测试中加以体现。长此以往,没有人敢去修改这些代码。

既然它在旧的系统中工作得很好,那么我们就没有理由去修改它们。当有新的需求出现时,我们就可以重新设计一个新的系统。

即使是最训练有素的开发团队,也不能保证始终编写出清晰高效的代码。

然而,如果我们不去尝试做一些改变,这些代码就会遗留下去——成为遗留代码,再次重构掉。即使说,重构系统是不可避免的一个过程,但是在这个过程中要是能抽象中领域特定的代码、语言也是件不错的事。

So,如何开始修改代码?如《修改代码的艺术》一书所说,应该是下面的五个步骤:

在有测试的情况下重构现有的代码才是安全的。而这些测试用例也是功能的体现,功能首先要得到保证了,然后才能保证一切都可以正常。不过,我更喜欢以下面三点概括他们:

  • 守: 找到测试点。守,即保证原有的功能是正确的。在这基础上,我们需要添加测试
  • 破: 打破依赖。会导致遗留代码的一个原因还有,原有代码的耦合度比较高。因此,我们需要去打破这些耦合,重新构建依赖。

不过,我想你只要有前面的那些步骤。你为什么还需要看这一章的内容呢?

  • 《持续交付指南:修改代码的9条最佳实践》

网站重构应包含结构、行为、表现三层次的分离以及优化,行内分工优化,以及以技术与数据、人文为主导的交互优化等。

从我所了解到的网站重构,它大概可以分为下面的几类:

下面就我们来看这三类的网站重构

通常来说对于速度的优化也包含在重构中

  • 程序的性能优化(如数据读写)
  • 采用 CDN 来加速资源加载
  • HTTP 服务器的文件缓存

如对于压缩前端资源这一类的重构,不仅可以从代码层级来解决问题,也可以借由服务器缓存来解决问题。这时候就需要去判断应该由哪个层级来做这样的事情——如果一件事可以简单地由机器来解决,但是由人来解决需要花费大量的时间,这时就应该交由机器来解决。而如果由人来解决是一个长期受益,并且成本比较低的事,那么就应该由人来解决。如我们只需要在我们的构建脚本中引入 minify 库就可以解决的事,那么应该交由人来做。

如,采用 CDN、HTTP 服务器的文件缓存这一类应该交由机器来做。

同时像程序性能优化、JS DOM 优化都是应交由人来解决的事。特别是像程序性能优化,从长期来看可能是一件长期受益的事。当且仅当,我们遇到性能问题时,重构这部分代码才可能带来优势。如果我们的网站访问量不是特别大,那么优化可能就是徒劳的。但是这种优化对于个人的成长还是挺有帮助的。

一般来说功能加强,应该是由于需求的变动才引起对系统的重构需求:

  • 解耦复杂的模块 -> 微服务
  • 针对内容创建或预留 API

深层次的网站重构应该考虑的方面

在刚开始接触架构设计的时候,对于这个知识点我觉得很奇怪。因为架构设计看上去是一个很复杂的话题,然而他是属于设计的一部分。如果你懂得什么是美、什么是丑,那么我想你也是懂得设计的。而设计是一件很有意思的事——刚开始写字时,我们被要求去临摹别人的字体,到了一定的时候,我们就可以真正的去设计。

总结在某种意义上相当于自己对自己的反馈:

当我们向自己输入更多反馈的时候,我们就可以更好地调整我们的方向。它属于输出的一部分,而我们也在不断调整我们的输入的时候,我们也在导向更好地输出。

为什么你不看不到自己的方向?

Retro,又可以称为回顾,它的目的是对团队的激励、改进。它的模式的特点就是让我们更关注于 Less Well,即不好的地方。当我们无法变得更好的时候,它可以帮助我们反观团队自身,即不要让现状变得更差,避免让破窗效应难以发生。

在敏捷团队里,Retro 通常会发生一个迭代的结束与下一个迭代的开始之间,这看上去就是我们的除旧迎新。相信很多人都会对自我进行总结,随后改进。而 Retro 便是对团队进行改进,即发生了一些什么不好的事,而这些事可以变好,那么我们就应该对此进行改进。

Retro 是以整个团队为核心去考虑问题的,通常来说没有理由以个人为对象。因为敏捷回顾有一个最高指导原则,即:

无论我们发现了什么,考虑到当时的已知情况、个人的技术水平和能力、可用的资源,以及手上的状况,我们理解并坚信:每个人对自己的工作都已全力以赴。

下面就让我们来看看在一个团队里是如何 Retro 的。

它不仅仅可以帮助我们发现团队里的问题,也可以集思广益的寻找出一些合适的解决方案。Retro 的过程和我们之前说的数据分析是差不多的,如下图所示:

  1. 设定会议目标。在会议最开始的时候我们就应该对会议的内容达成一种共识,我们要回顾的主题是啥,我们要回顾哪些内容。如果是一般性的迭代 Retro,那么我们的会议主题就很明显了。如果是针对某一个特定项目的 Retro,那么主题也很明显。
  2. Retro 的回顾。即回顾上一个 Retro 会议的 Action 情况,并进行一个简单的小结。
  3. 收集数据。收集数据需要依赖于我们收集数据的模式,要下面将会说到的四种基本维度,或者是雷达图等等。不同的收集数据的形式有不同的特别,团队里的每个人都应该好好去参与。
  4. 激发灵感。当我们寻找到团队中一个值得去庆祝的事,或者一个出了问题的事,我们就应该对这个问题进行讨论。并且对其展开了解、调查,让大家进一步看到问题,看到问题的根源。
  5. 决定做什么。现在我们已经做了一系列的事,最重要的来了,就是决定我们去做什么。我们应该对之前的问题做出怎样的改进。
  6. 总结和收尾。记录会议成果,更新文档等等。

以我们为例,我们以下面的三个维度去进行 Retro:

当然最后还会有一个Action:

该模式的特点是会让我们更多的关注 Less Well,关注我们做的不好的那些。

Well。我们在 Well 里记录一些让我们开心的事,如最近天气好、迭代及时完成、没有加班等等,这些事从理论上来说应该继续保持(KEEP)下去。

Less Well。关注于在这个迭代的过程中,发生了一些什么不愉快的事。一般来说,我们就会对 Less Well 加以细致的讨论,找出问题的根源,并试图找到一个解决方案。换句话来说,就是改变(CHANGE)。

Suggestion/Puzzle。如果我们可以直接找到一些建议,那么我们就可以直接提出来。并且如果我们对当前团队里的一些事情,有一些困惑那么也应该及早的提出来。

Action。当我们对一些事情有定论的时候,我们就会提出相应的 Action。这些 Action 应该有相应的人去执行,并且由团队来追踪。

在刚开始接触架构设计的时候,我买了几本书然后就开始学习了。我发现在这些书中都出现了一些相似的东西,如基本的分层设计、Pipe and Filters 模式、MVC 模式。然后,我开始意料到这些模式本身就是最好的架构。

MVC 模式本身也是基于分层而设计的,如下图是 Spring MVC 的请求处理过程:

而这只是框架本身的架构,这一类也是我们预先设计好的框架。

在框架之上,我们会有自己本身的业务所带来的模式。如下图是我在网上搜罗到的一个简单的发送邮件的架构:

这样的模式则是在业务发展的过程中演进出来的。

日常使用的框架多数是预先设计的构架,因为这些架构本身的目标是明确的。系统会围绕一定的架构去构建,并且在这个过程中架构会帮助我们更好地理解系统。如下图所示的是 Emacs 的架构:

它采用的是交互式应用程序员应用广泛的模型-视图-控制器模式。

无论是瀑布式开发——设计好系统的框架,然后对系统的每个部分进行独立的完善和设计,最后系统再集成到一起。还是敏捷式开发——先做出 MVP,再一步步完善。它们都需要一定的预先式设计,只是传统的开发模式让两者看上去是等同的。

在过去由于 IT 技术变革小,新技术产生的速率也比较低,预先设计系统的架构是一种很不错的选择。然而,技术的发展趋势是越来越快,现有的设计往往在很短的一段时间里就需要推倒重来。

演进式架构则是我们日常工作的业务代码库演进出来的。由于业务本身在不断发展,我们不断地演进系统的架构。在这样的模式下产生的架构系统会更加稳定,也更加优美。仅仅依赖于事先的设计,而不考虑架构在后期业务中的变化是一种不可取的设计模式。

这不并意味着不采用预先式设计,而是不一味去依靠原先系统的架构。

设计模式不是一开始就有的,好的软件也不是一开始就设计成现在这样的,好的设计亦是如此。

导致我们重构现有系统的原因有很多,但是多数是因为原来的代码变得越来越不可读,并且重构的风险太大了。在实现业务逻辑的时候,我们快速地用代码实现,没有测试,没有好的设计。

而下图算是最近两年来想要的一个答案:

浮现式设计是一种敏捷技术,强调在开发过程中不断演进。软件本身就不应该是一开始就设计好的,它需要经历一个演化的过程。

和 Growth 一样,在最开始的时候,我不知道我想要的是怎样的——我只有一个想法以及一些相对应的实践。接着我便动手开始做了,这是我的风格。不得不说这是结果导向编程,也是大部分软件开发采用的方法。

所以在一开始的时候,我们就有了下面的代码:

还好我们在一开始的时候写了一些测试,这让我们可以有足够的可能性来重构代码,而使其不至于变成遗留代码。这也是我们推崇的一些基本实践:

测试是系统不至于腐烂的一个后勤保障,除此我们还需要保持对于 Code Smell 的嗅觉。如上代码:

上面代码中的“250”指的到底是?这样的数字怎么能保证别人一看代码就知道250到底是什么?

而在最开始的时候我们想不到这样的结果。最初我们的第一直觉都是一样的,然而只要我们保持着对 Code Smell 的警惕,情况就会发生更多的变化。

重构是区分普通程序员和专业程序员的一个门槛,也是练习得来的一个结果。

如果你还懂得一些设计模式,那么想来,软件开发这件事就变得非常简单——我们只需要理解好需求即可。

从一开始就使用模式,要么你是专家,要么你是在自寻苦恼。模式更多的是一些实现的总结,对于多数的实现来说,它们有着诸多的相似之处,可以使用相同的模式。

而在需求变化的过程中,一个设计的模式本身也是在不断的改变。如果我们还固执于原有的模式,那么我们就会犯下一个又一个的错误。

在适当的时候改变原有的模式,进行一些演进变显得更有意义一些。如果我们不能在适当的时候引进一些新的技术,那么旧有的技术就会不断累积。这些技术债就会不断往下叠加,这个系统将会接近于崩塌。而我们在一开始所设定的一些业务逻辑,也会随着系统而逝去,这个公司似乎也要到尽头了。

而如果我们可以不断地演进系统——抽象服务、拆分模块等等。业务就可以在技术不断演进地过程中得以保留。

每一个程序员都是架构师。平时在我们工作的时候,架构师这个 Title 都被那些非常有经历的开发人员占据着。然而,如果你喜欢刷刷 Github,喜欢做一些有意思的东西,那么你也将是一个架构师。

如果你需要帮人搭建一个博客你先会想到什么?

先问一个问题,如果要让你搭建一个博客你会想到什么技术解决方案?

  1. 动态博客(可以在线更新,如 WordPress)
  2. 半动态的静态博客(可以动态更新,但是依赖于后台构建系统)

这只是基本的骨架。因此如果只有这点需求,我们无法规划出整体的方案。现在我们又多了一点需求,我们要求是独立的博客,这样我们就把第4个方案去掉了。但是就现在的过程来说,我们还是有三个方案。

接着,我们就需要看看 Ta 需要怎样的博客,以及他有怎样的更新频率?以及他所能接受的价格?

先说说价格——从价格上来说,静态博客是最便宜的,可以使用 AWS S3 或者国内的云存储等等。从费用上来说,一个月只需要几块钱,并且快速稳定,可以接受大量的流量访问。而动态博客就贵了很多倍——我们需要一直开着这个服务器,并且如果用户的数量比较大,我们就需要考虑使用缓存。用户数量再增加,我们就需要更多地服务器了。而对于半动态的静态博客来说,需要有一个 Hook 检测文章的修改,这样的 Hook 可以是一个客户端。当修改发生的时候,运行服务器,随后生成静态网页。最后,这个网页接部署到静态服务器上。

从操作难度上来说,动态博客是最简单的,静态博客紧随其后,半动态的静态博客是最难的。

不是一个主域名) 。另外,你要尽可能多的联系,以包含适当的替代文字。你的想法。
依赖于更新频率 几元~几十元
基于 git 的数据库

现在,我们已经达成了一定的共识,有了几个方案可以供用户选择。而这时,我们并不了解进一步的需求,只能等下面的结果。

客户需要可以看到文章的修改变化,这时就去除了静态博客。现在还有第1和第3种方案可以选,考虑到第3种方案实现难度比较大,不易短期内实现。并且第3种方案可以依赖于第1种方案,就采取了动态博客的方案。

但是,问题实现上才刚刚开始。

作为一个团队,我们需要优先考虑这个问题。使用怎样的技术解决方案?而这是一个更复杂的问题,这取决于我们团队的技术组成,以及未来的团队组成。

如果在现有的系统中,我们使用的是 Java 语言。并不意味着,每个人都喜欢使用 Java 语言。因为随着团队的变动,做这个技术决定的那些人有可能已经不在这个团队里。即使那些人还在,也并不意味着我们喜欢在未来使用这个语言。当时的技术决策都是在当时的环境下产生的,在现在看来很扯的技术决策,有可能在当时是最好的技术决策。

对于一个优秀的团队来说,不存在一个人对所有的技术栈都擅长的情况——除非这个团队所从事的业务范围比较小。在一个复杂的系统里,每个人都负责系统的相应的一部分。尽管到目前为止并没有好的机会去构建自己的团队,但是也希望总有一天有这样的机会。在这样的团队里,只需要有一个人负责整个系统的架构。其余的人可以在自己擅长的层级里构建自己的架构。因此,让我们再回到我们的博客中去,现在我们已经决定使用动态的博客。然后呢?

作为一个博客我们至少有前后台,这样我们可能就需要两个开发人员。

(PS:当然,我们也可以使用 React,但是在这里先让我们忽略掉这个框架,紧耦合会削弱系统的健壮性。)

接着,作为一个前端开发人员,我们还需要考虑的两个问题是:

  1. 我们的博客系统是否是单页面应用?
  2. 要不要做成响应式设计

第二个问题不需要和后台开发人员做沟通就可以做决定了。而第一个问题,我们则需要和后台开发人员做决定。单页面应用的天然优势就是:由于系统本身是解耦的,他与后台模板系统脱离。这样在我们更换前端或者后台的时候,不需要去考虑使用何种技术——因为我们使用 API 作为接口。现在,我们决定做成单页面应用,那么我们就需要定义一个 API。之后,我们就可以决定在前台使用何种框架:

在这时,后台人员也可以自由地选择自己的框架、语言。后台开发人员只需要关注于生成一个 RESTful API 即可,而他也需要一个好的 Model 层来与数据库交付。

现在,我们似乎已经完成了大部分的工作?我们还需要:

相信看完之前的章节,你也有了一定的经验了,也可以成为一个架构师了。

-《程序员必读之软件架构》

解耦是一件很有意思的过程,它也能反应架构的变迁。

在我初识架构是什么的时候,我看到了 MVC 模式架构。这种模式是基于分层的结构,要理解起逻辑也很简单。这个模式如下图所示:

将传到前端框架中渲染,最后再返回给浏览器。

但是这样的架构充满了太多的问题,如 view 与 controller 的紧密耦合、controller 粒度难以把控的问题等等。

我使用 Django 差不多有四年了,主要是用在我的博客上。与 MVC 模式一对比,我发现 Django 在分层上还是很有鲜明特性的:

从上面的对比中,我们可以发现 Django 把 View 分层了。以 Django 对于 MVC 的解释来说,视图用来描述要展现给用户的数据。 而在 ROR 等其他的 MVC 框架中,控制器负责决定向用户展现哪些数据,而视图决定如何展现数据。

联想起我最近在学的 Scala 中的 Play 框架,我发现了其中诸多的相似之处:

Play 框架异步请求

不过与一般 MVC 架构的最大不同之处,怕是在于 Django 的 APP 架构。Django 中有一个名为 APP 的概念,它是实现某种功能的Web 应用程序。如果我们要设计一个博客系统的话,那么在这个项目中,Blogpost 是一个 APP、评论是一个 APP、用户管理是一个 APP等等。每个 APP 之中,都会有自己的 Model、View 和

当我们需要创建一个新的功能的时候,我们只需要创建一个新的 APP 即可——为这个 APP 配置新的 URL、创建新的 Model 以及新的 View。如果功能上没有与原来的代码重复的话,那么这就是一个独立的 APP,并且我们可以将这个 APP 的代码 Copy/Paste 到一个新的项目中,并且不需要做修改。

与一般的 MVC 架构相比,我们会发现我们细化了这些业务逻辑原来的三层结构,会随着 APP 的数量发生变化。如果我们有三个 APP 的话,那么我们相当于有3*三层,但是他不是等于九层。这样做可以从代码上直接减少逻辑的思考,让我们可以更加集中注意力于业务实现,同时也利于我们后期维护。

虽是如此,后来我意识到了这样的架构并没有太多的先进之处。而这实际上是一个美好但是不现实的东西,因为我们还是使用同一个数据库。

在微服务架构中,提倡将单一应用程序划分成一组小的服务,这些服务之间互相协调、互相配合。每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制互相沟通。每个服务都应该有自己独立的数据库来存储数据。

Django 从某种意义上有点接近微服务的概念,只是实际上并没有完全实现。因为它没有实现 Play 框架的异步请求机制。抱句话来说,应用很容易就会在调用 JDBC、Streaming API、HTTP 请求等一系列的请求中发生阻塞。

这些服务都是独立的,对于服务的请求也是独立的。使用微服务来构建的应用,不会因为一个服务的瘫痪让整个系统瘫痪。最后,这一个个的微服务将合并成整个系统。

我们将后台的服务变成微服务的架构,在前台使用 Reactive 编程,这样就可以结合两者的优势,解耦出更好的架构模式。然而,这其中还有一个让人不爽的问题,即数据库。如果我们使用多个数据库,那么维护成本也随着上升。而如果我们可以在后台使用类似于微服务的 Django MTV 架构,并且它可以支持异步请求的话,并在前台使用 Reactive 来编程,是不是就会更爽一点?

对于复杂的系统来说,上面的做法做确实很不错。但是对于一个简单地系统来说,这样做是不是玩过火了?如果我们要设计一个博客系统的话,是不是可以考虑将 Write/Read 分离就可以了?

对于这个架构的深入思考是起源于之前在理解 DDD。据说在 DDD 领域中被广泛使用。理解 CQRS 可以从分离 Model 和 API 集合来处理读取和写入请求开始,即 CQS(Command Query Separation,命令查询分离)模式。CQS 模式最早由软件大师Bertrand Meyer(Eiffel语言之父,面向对象开-闭原则 OCP 提出者)提出。他认为,对象的行为仅有两种:命令和查询。

这个类型的架构如下图所示:

除了编写优化的查询类型,它可以让我们轻松换 API 的一部分读一些缓存机制,甚至移动读取 API 的请求到另一台服务器。

对于读取和写入相差不多的应用来说,这种架构看起来还是不错的。而这种架构还存在一个瓶颈问题,使用同一个 RDBMS。对于写入多、读取少的应用来说,这种架构还是存在着不合理性。

为了解决这个问题,人们自然是使用缓存来解决这个问题了。我们在我们的应用服务外有一个 HTTP 服务器,而在 HTTP 服务器之外有一个缓存服务器,用于缓存用户常驻的一些资源。如下图所示:

带缓存的 Web 架构

而实际上这样的服务器可能是多余的——我们为什么不直接生成HTML就好了?

或许你听过 Martin Folwer 提出的编辑-发布分享式架构:即文章在编辑时是一个形式,而发表时是另一个形式,比如用 Markdown 编辑,而用 HTML 发表。

而最典型的应用就是流行于 GitHub 的 Hexo、Jekyll 框架之类的静态网站。如下图所示的是 Hexo 的工作流:

我们在本地生成我们的项目,然后可以创建一个新的博客、开始编写内容等等。接着,我们可以在本地运行起这个服务,除了查看博客的内容,还可以修改样式等等。完成上面的工作后,我们就可以生成静态内容,然后部署我们的应用到GitHub Page上。这一切看上去都完美,我们有两个不同的数据源——一个是 md 格式的文本,一个是最后生成的 HTML。它们已经实现了读写/分离:

但是作为一个前端开发人员,没有 JSON,用不了 Ajax 请求,我怎么把博客做成一个单页面应用?

我们需要将博客转为 JSON,而不是一个 hexo 之类的格式。有了这些 JSON 文件的存在,我们就可以把 Git 当成一个 NoSQL 数据库。同时这些 JSON 文件也可以直接被当成 API 响应来使用。

其次,这些博客还需要像 hexo 一样生成 HTML。

并且,开发人员在开发的时候不会影响到编辑的使用,于是就有了下面的架构:

基于 Git 的编辑-发布分离

在这其中我们有两种不同的数据形式,即存储着 Markdown 数据的 JSON 文件和最后生成的 HTML。

对博客数量不是很大的网站,或者说一般的网站来说,用上面的技术都不是问题。然而有大量数据的网站怎么办?使用 EventBus:

在我之前玩的一个 Demo 中,使用 Python 中的 Scrapy 爬虫来抓取现有的动态网站,并将其变成静态网站部署到 AWS S3上。

但是上面仅仅只是实现了文章的显示,我们还存在一些问题:

等等的这些服务是没有用静态 API 来实现的。

既然可以有这么多分法,并且我们都已经准备好分它们了。那么分了之后,我们就可以把他们都合到一起了。

最常见的解耦应用的方式中,就有一种是基于 Nginx 来分发 URL 请求。在这种情况下,对于 API 的使用者,或者最终用户来说,他们都是同一个 API。只是在后台里,这个 API 已经是不同的几个 API 组成,如下图所示:

对于我们想要设计的系统来说也是如此,我们可以通过这个 Dispatcher 来解耦我们的服务。

现在,我们想要的系统的雏形已经出现了。

从源头上来说,我们把能缓存的内容变成了静态的 HTML,通过 CDN 来分发。并且,我们还可以将不同的服务独立出来。

从实现上来说,我们将博客的数据变成了两部分: 一个以 Git + JSON 格式存在的 API,它除了可以用于生成 HTML,另外一部分作为 API 来使用。

    • 使用场景:当无法对一个参数的类型使用接口提取,或者该参数难以被"伪装"时.
    • 例如,该参数的类型是一个含有很多方法的接口类型.在进行单元测试时必须编写一个实现该接口的实现类.
    • 问题:从维护的角度来看,传递了一个宽接口,而其实方法内部只使用了该接口的部分契约.
    • 所以,应该尽量使用窄接口来替代宽接口.
    • 使用场景:若方法规模太大,或者使用了实例变量或者其他方法.
    • 将大方法移到一个新类中,因为该类仅含有一个方法成员,所以称为方法对象.
    • 可以方便地对方法对象进行测试.
    • 旧方法中的局部变量,可以作为方法对象中的成员变量.
    • 使用场景:需要对依赖于全局变量的代码进行测试.
    • 目的:让所依赖的全局变量在测试期间具有另一种行为.
    • 方式:使用链接器,连接到另一个全局变量的定义上.对它进行封装,进而解耦.
    • 方法:建立一个类来持有被依赖的全局变量.
    • 方式:对于没有使用实例变量的方法,可以直接将其修改为static,这样方便进行测试(不用new类的实例).
    • 实际上,静态数据和成员方法都不属于所在类,应该算是"临时场地".
    • 使用场景:解开对全局变量和static方法的依赖,且对一个全局对象没有太多位于不同位置的调用.
      • 确定要提取的调用,找出其方法签名.
      • 使用该方法签名创建一个新的方法.
      • 把对目标方法(要提取的)的调用复制到新方法中.
      • 然后在调用的地方,修改为调用该新方法.
    • 使用场景:构造函数中对其成员变量的初始化工作可能会造成难以测试.
    • 前提:必须支持构造函数中的虚方法的多态.
      • 找出构造函数中初始化成员变量的代码.
      • 将这些代码都转移到一个工厂方法中,且该方法为virtual.
      • 创建一个继承自被测试类的新类,并重写工厂方法.
    • 使用场景:类似于工厂方法的场景,同时,构造函数中仅构造成员变量,但是并不使用它.
      • 找出需要引入获取方法的对象.
      • 将创建该对象所需的所有逻辑提取到一个获取方法中.
      • 将所有使用该对象的代码都替换为通过该获取方法.并在构造函数中将该对象的引用置为null.
      • 在获取方法中加入"首次调用时创建"功能.
      • 子类化该类,重写该获取方法,并在其中提供Fake对象.
    • 使用场景:如果静态方法中包含了不想在测试时依赖的环境.静态粘着.
      • 找出会在测试中造成问题的静态方法.
      • 在其所属类中新建一个实例方法(保持静态方法的签名).在该实例方法内,直接调用静态方法.
      • 找出纳入测试的类中,那些地方使用了该静态方法.将其修改为对新的实例方法的调用.
    • 创建一个哑元库,该库内包含跟目标函数集合拥有完全一样签名的函数声明.
    • 然后在测试环境中,使用该哑元库来替代真实的类库.

我要回帖

更多关于 零基础如何写代码 的文章

 

随机推荐