为什么优秀的程序员不喜欢GUI

优秀的程序员 要给优秀的程序员下一个明确的定义无疑是一件非常困难的事情。擅长抽象思维,动手能力强,追求效率,喜欢自动化,愿意持续学习,对代码质量有很高的追求等等,这些维度都有其合理性,不过又都略显抽象和主观。 我对于一个程序员是否优秀,也有自己的标准,那就是TA对命令行的熟悉/喜爱程度。这个特点可以很好的看出TA是否是一个优秀的(或者潜在优秀的)程序员。我周围就有很多非常牛的程序员,无一例外的都非常擅长在命令行中工作。那什么叫熟悉命令行呢?简单来说,就是90%的日常工作内容可以在命令行完成。 当然,喜欢/习惯使用命令行可能只是表象,其背后包含的实质才是优秀的程序员之所以优秀的原因。 自动化 Perl语言的发明之Larry Wall有一句名言: The three chief virtues of a programmer are: Laziness, Impatience and Hubris. – Larry Wall 懒惰(Laziness)这个特点位于程序员的三大美德之首:唯有懒惰才会驱动程序员尽可能的将日常工作自动化起来,解放自己的双手,节省自己的时间。相比较而言GUI应用,不得不说,天然就是为了让自动化变得困难的一种设计(此处并非贬义,GUI有着自己完全不同的目标群体)。GUI更强调的是与人类的直接交互:通过视觉手段将信息以多层次的方式呈现,使用视觉元素进行指引,最后系统在后台进行实际的处理,并将最终结果以视觉手段展现出来。 这种更强调交互过程的设计初衷使得自动化变得非常困难。另一方面,由于GUI是为交互而设计的,它的响应就不能太快,至少要留给操作者反应时间(甚至有些用户操作需要人为的加入一些延迟,以提升用户体验)。 程序员的日常工作 程序员除了写代码之外,还有很多事情要做,比如自动化测试,基础设施的配置和管理,持续集成/持续发布环境,甚至有些团队好需要做一些与运维相关的事情(线上问题监控,环境监控等)。 开发/测试 基础设施管理 持续集成/持续发布 运维(监控)工作 娱乐 而这一系列的工作背后,都隐含了一个自动化的需求。在做上述的工作时,优秀的程序员会努力的将其自动化,如果有工具就使用工具;如果没有,就开发一个新的工具。这种努力让一切都尽可能自动化起来的哲学起源于UNIX世界。 而UNIX哲学的实际体现则是通过命令行来完成的。 Where there is a shell, there is a way. UNIX编程哲学 关于UNIX哲学,其实坊间有多个版本,这里有一个比较详细的清单。虽然有不同的版本,但是有很多一致的地方: 小即是美 让程序只做好一件事 尽可能早地创建原型(然后逐步演进) 数据应该保存为文本文件 避免使用可定制性低下的用户界面 审视这些条目,我们会发现它们事实上促成了自动化一切的可能性。这里列举一些小的例子,我们来看看命令行工具是如何通过应用这些哲学来简化工作,提高效率的。一旦你熟练掌握这些技能,就再也无法离开它,也再也忍受不了低效而复杂的各种GUI工具了。 命令行如何提升效率 一个高阶计算器 在我的编程生涯的早期,读过的最为振奋的一本书是《UNIX编程环境》,和其他基本UNIX世界的大部头比起来,这本书其实还是比较小众的。我读大二的时候这本书已经出版了差不多22年(中文版也已经有7年了),有一些内容已经过时了,比如没有返回值的main函数,外置的参数列表等等,不过在学习到HOC(High Order Calculator)的全部开发过程时,我依然被深深的震撼到了。 简而言之,这个HOC语言的开发过程需要这样几个组件: 词法分析器lex 语法分析器yacc 标准数学库stdlib 另外还有一些自定义的函数等,最后通过make连接在一起。我跟着书上的讲解,对着书把所有代码都敲了一遍。所有的操作都是在一台很老的IBM的ThinkPad T20上完成的,而且全部都在命令行中进行(当然,还在命令行里听着歌)。...

January 17, 2017 2 min

为什么DDD很难?

DDD为什么很难实施 领域驱动设计(Domain Driven Design)的概念已经被发明了十多年,而且也不乏相关著作,但是业界宣称自己应用了DDD原则的项目,软件却鲜有耳闻。随着微服务架构的流行,DDD在边界识别,服务划分等方面不断被提及,作为一种应对复杂软件的方法论,似乎又被重视起来了。 那么,为什么这个听起来很靠谱的方法论实际上很难实施呢?我们以DDD创始人Eric Evans的经典书籍《领域驱动设计:软件核心复杂性应对之道》为例,分析一下,可能的原因是这样的: 新概念数量比较多 战术/战略都有所涉及 不同层次的概念混杂(除了设计模式之外,还有架构风格的讨论)在一起 繁复而混杂的概念 领域,子域,核心子域,通用子域,实体,值对象,领域服务,应用服务,领域事件,统一语言,衔接上下文,遵循者等等。DDD中有着大量的新概念,而且这些概念有些是技术相关的,有些是问题相关的,交织在一起之后,很难理清头绪。 DDD中的模式又分为战术的和战略的两大部分,有很多团队应用了战术相关的,比如实体,值对象,领域服务,仓库等模式,代码看似DDD,实则与DDD强调的以领域为中心相去甚远,陷入了开发者太过于关注技术本身的老路上,这种现象还有个专门的名词,叫DDD-Lite。 不同的思维方式要求 DDD要求读者既有具体Coding的技能,有需要跳出圈外,以架构师的角度来审视整个系统。DDD需要开发者以一个全新的视角去认识软件开发,强调对业务流程的熟悉,强调与领域专家一起协作,强调软件的表达能力和进化能力。而这些思维方式的转变,都有很大阻力的(想想从面向对象世界切换到函数式编程,再到响应式函数编程的切换)。 It should be noted that no ethically-trained software engineer would ever consent to write a DestroyBaghdad procedure. Basic professional ethics would instead require him to write a DestroyCity procedure, to which Baghdad could be given as a parameter. – Nathaniel Borenstein 我自己在工作的前4年,非常反感业务,认为case by case的业务流程会严重影响我的代码的通用性。然而事实是,纯粹通用的代码是不存在的。毕竟,诸如IoC容器,Web等基础设施已经相当完善,完全无需我们重复发明轮子。而作为应用开发人员,更重要的是在充分理解业务的前提下,写出易于维护,易于扩展,可以更快速响应业务变化的代码。 正因为如此,DDD在一定程度上会让程序员感到不适,它太强调领域了,而领域又是独一无二的,每个公司的每个系统都有其独立性,这就要求你的代码可能没法做到纯粹。 正确姿势 要解决上面提到的这些问题,正确的实施DDD来指导实际的开发工作,需要至少做到这样几个事情。首先,明确分清问题和方案(这是初学DDD者最容易犯的错误);其次,转变思维方式,将软件开发的重心放在梳理并明确业务需求上,而不是代码逻辑上;最后,需要应用一些合理的工程实践来促成DDD-Lite的落地。 分清问题和解决方案 我们说领域(子域,通用子域,支撑子域等)的时候,是在讨论问题域,即定义我们要解决的问题是什么。而说到限界上下文,聚合,实体,仓库则是在讨论解决方案部分,人们很容易将两者搞混。 在电商的领域中,一些典型的问题是: 如何获得更多的客户 如何让客户更快速的找到自己想要的商品 如何准确的推荐相关产品给客户 用户如何付费 要解决这些问题,人们可能会开发出来一个软件系统,也可能会用手工的流程,也可能是混合模式。同样,一些具体的解决方案的例子是:...

January 11, 2017 2 min

软件开发为什么很难

问题的分类 最初在1999年被Dave Snowden开发出来的**Cynefin框架**尝试把世界上的问题划分到了5个域中(大类): 简单(Simple)问题,该域中的因果关系非常明显,解决这些问题的方法是 感知-分类-响应(Sense-Categorise-Respond),有对应的最佳实践 复合(Complicated)问题,该域中的因果关系需要分析,或者需要一些其他形式的调查和/或专业知识的应用,解决这些问题的方法是感知-分析-响应(Sense-Analyze-Respond),有对应的好的实践 复杂(Complex)问题,该域中的因果关系仅能够从回顾中发现,解决这些问题的方法是探索-感知-响应(Probe-Sense-Respond),我们能够感知涌现实践(emergent practice) 混乱(Chaotic)问题,该域中没有系统级别的因果关系,方法是行动-感知-响应(Act-Sense-Respond),我们能够发现新颖实践(novel practice) 失序(Disorder)问题,该域中没有因果关系,不可感知,其中的问题也无法被解决 显然,软件开发过程更多地是一个复杂(Complex)问题。在一个产品被开发出来之前,不确定性非常高,团队(包括业务人员和技术人员)对产品的知识也是最少的,而且需要大量的学习和尝试才可以明确下一步可能的方向。不幸的是,很多时候我们需要在一开始(不确定性最高的时候)就为项目做计划。这种从传统行业中非常适合的方法在软件开发领域不再适用,这也是敏捷开发、精益等方法论在软件开发中更加适合的原因。 来源:http://alistair.cockburn.us/Disciplined+Learning 正因为软件开发事实上是一个学习的过程,我们学习到的新知识反过来会帮助我们对问题的定义,从而带来变化。这里的变化可能来自两个方向: 功能性 非功能性 功能性的变化指随着对业务的深入理解、或者已有业务规则为了匹配市场而产生的变化。比如支付方式由传统的货到付款变成了网银付款,又变成了微信支付、支付宝扫码等等。一个原始的电商平台仅仅提供基本的购物服务,但是后来可以根据已有数据产生推荐商品,从来带来更大的流量。这些变化需要体现在已有的代码中,而对代码的修改往往是牵一发而动全身。 非功能性的变化是指随着业务的发展,用户规模的增加,数据量的变化,安全认知的变化等产生的新的需求。比如100个用户的时候无需考虑性能问题,但是100万用户的时候,性能就变成了必须重视的问题。天气预报应用的数据安全性和网络银行的数据安全性要求也大不相同。 而在业务提出一个需求的时候,往往只是一个简化过的版本。 非功能性复杂性 来源:https://d13yacurqjgara.cloudfront.net/users/749341/screenshots/2228676/uielements_day021_dribbbleinvites.jpg 这是一个经过设计师精确设计的界面,在它被设计出来之前,用户事实上无法准确的描述出它。设计过程中经历了很多的诸如: 线框图 颜色的确定 交互的动画 信息层次 往复多次之后,界面确定了。在没有仔细思考使用场景的时候,开发会误以为这个功能非常简单。但是如果你是一个有经验的开发者,很快会想到的一些问题是: 在宽屏下如何展示 在平板上如何展示 在手机上如何展示 即使仅仅支持桌面版,跨浏览器要考虑吗?支持哪些版本? 有些UI效果在低版本的浏览器上不工作,需要Shim技术 除此之外,依然有大量的其他细节需要考虑: 性能要求是什么样的? 安全性要考虑吗? 在网络环境不好的时候,要不要fallback到基础视图? 既然涉及发送邀请函,送达率如何保证 与外部邮件服务提供商集成时的工作量 等等。这些隐含的信息需要被充分挖掘出来,然后开发者才能做一个合理的评估,而且这还只是开始。一旦进入开发阶段,很多之前没有考虑到的细节开始涌现:字体的选用,字号,字体颜色,元素间的间距等等,如何测试邮件是否发送成功,多个角色之间的conversation又会消耗很多时间。 需求的变化方向 作为程序员,有一天你被要求写一段代码,这段代码需要完成一件很简单的事: 打印"Hello, world"5次 很容易嘛,你想,然后顺手就写下了下面这几行代码: print("Hello, world") print("Hello, world") print("Hello, world") print("Hello, world") print("Hello, world") 不过,拷贝粘贴看起来有点低端,你做了一个微小的改动: for(var i = 0; i < 5; i++) { print("Hello, world") } 看起来还不错,老板的需求又变成了打印"Goodbye, world"5次。既然是打印不同的消息,那何不把消息作为参数呢?...

January 6, 2017 1 min

我的2016

大事记 把女儿心心哄睡着之后,我躺在她旁边,听着她平稳的呼吸声,心里充满了幸福和感激。女儿降生的那种感动,是无以伦比的,之前的很多事情变得无所谓起来,这当然是我今年最大的收获。 当然,初为人父,有好多好多东西需要学习,她还在月子里的时候,我整理过一次思维导图: 今年也有一些其他的里程碑: 骑行了第一个100公里 学会了驾驶(已经行驶了近6000公里) 学会了抱娃,换尿布,给娃洗澡,试水温等等 《轻量级Web应用开发》被翻译成繁体中文版在台湾发售 Hiking到了秦岭最高点(大爷海,海拔3500m) 学习 今年的大部分时间都不在办公室,在客户现场出差有8-9个月。最大的感受是归属感的缺乏,另外就是大部分时候再输出,会导致积累的存货快要被掏空的感觉。很多知识需要花时间来补充上,咨询工作本身有很大一部分是需要咨询师在非咨询项目上做积累,然后再去输出的。 这一点希望在2017年可以做的更好一些:减少一些出差,多在办公室的项目上工作,然后能有更多的时间来积累自己的知识库。 上半年花了一些时间和设计师唐婉莹合写的《在迭代1之前》,后来出差网络比较封闭的时候,就只有唐婉莹在做更新(频率和质量都很高),我自己的开发部分很久没有改动了,算是一个比较遗憾的地方。2017年希望可以完成其中的烂尾部分。 总体来说,2016年很多时间是做咨询工作,内容比较杂乱。我梳理了一下,大致如下: 一些微小的练习 我的第一个React Natvie应用,感谢傅若愚: 闪电计划的页面局部,感谢张小虫: Graphviz一个例子: 闪电计划 7月,和另外两个同事组织了一个系列的活动,这个活动旨在通过一系列的刻意练习,包括但不限于: TDD,Tasking,构建,环境自动化 自动化测试(集成测试,UI测试) 项目中的常见场景(多表关联,异常处理,RESTful API设计) 常见静态页面 一些具体而微的端到端Web项目 闪电计划的另一个副作用是找出了一些前端的Kata,可以供后续的前端们做练习用。我已经找张小虫,李彤欣做过了第一次的实验,证明是一个比较合理的方向。在2017年,可以作为新的Workshop进行。 项目经历 元月份经历了一个史上最奇特的咨询项目:Staffing好的3个人中,有两个离职了……。不过还好,通过两个月的微小工作,客户最终还是比较满意的,也很顺利的有了项目的第二期(虽然由于其他原因,第二期只做了一半)。 其他的经历比较平淡: 在回南天的3月去了一趟广州出差,做了一些前端性能相关的测试。 在最热的7月份去了一趟上海出差,莫名其妙的做了一些前端性能相关的测试,以及React Native的一些预研。 最后在寒冷的12月去了一趟深圳出差,做了一些安全测试(基于OWASP)、依赖关系图(基于Doxygen)等的Spike。 不过总体来看,2016年经历的项目是最多的,而且多样性也很高: 3个咨询项目 3个交付项目 3个售前 其他 应王欢要求,除了一起吃过几次饭之外,今年跟王欢基本上没有交集。倒是多亏了他的脸,在楼下的来福士和他喝了不少次咖啡。 这是九月份去付莹家的黄河时和同事们的合影,第一次开车走高速去那么远的地方,后座还有一个澳洲的客户。 骑行了一半儿,有一群开着车的同事追了上来,跟我们一起吃了个饭,我们骑车回去,他们则开车/坐车回去了。

December 28, 2016 1 min

微服务中的测试

新的挑战 微服务和传统的单块应用相比,在测试策略上,会有一些不太一样的地方。简单来说,在微服务架构中,测试的层次变得更多,而且对环境的搭建要求更高。比如对单块应用,在一个机器上就可以setup出所有的依赖,但是在微服务场景下,由于依赖的服务往往很多,要搭建一个完整的环境非常困难,这对团队的DevOps的能力也有比较高的要求。 相对于单块来说,微服务架构具有以下特点: 每个微服务在物理上分属不同进程 服务间往往通过RESTful来集成 多语言,多数据库,多运行时 网络的不可靠特性 不同的团队和交付周期 上述的这些微服务环境的特点,决定了在微服务场景中进行测试自然会面临的一些挑战: 服务间依赖关系复杂 需要为每个不同语言,不同数据库的服务搭建各自的环境 端到端测试时,环境准备复杂 网络的不可靠会导致测试套件的不稳定 团队之间的沟通成本 测试的分层 相比于常见的三层测试金字塔,在微服务场景下,这个层次可以被扩展为5层(如果将UI测试单独抽取出来,可以分为六层)。 单元测试 集成测试 组件测试 契约测试 端到端测试 和测试金字塔的基本原则相同: 越往上,越接近业务/最终用户;越往下,越接近开发 越往上,测试用例越少 越往上,测试成本越高(越耗时,失败时的信息越模糊,越难跟踪) 单元测试 单元测试,即每个微服务内部,对于领域对象,领域逻辑的测试。它的隔离性比较高,无需其他依赖,执行速度较快。 对于业务规则: 商用软件需要License才可以使用,License有时间限制 需要License的软件在到期之前,系统需要发出告警 @Test public void license_should_expire_after_the_evaluation_period() { LocalDate fixed = getDateFrom("2015-09-03"); License license = new License(fixed.toDate(), 1); boolean isExpiredOn = license.isExpiredOn(fixed.plusYears(1).plusDays(1).toDate()); assertTrue(isExpiredOn); } @Test public void license_should_not_expire_before_the_evaluation_period() { LocalDate fixed = getDateFrom("2015-09-05"); License license = new License(fixed....

October 1, 2016 2 min