有一种说法,未来的程序都是机器生成的,程序员将成为一种古老的职业种族。这种事在人类历史上并不罕见。历史上有很多一开始都是靠人做的职业,最后都由于技术的更新换代而成为了一种被人遗忘的艺术。
就算是在编程领域,如今大部分人也不必写汇编代码。但是由于抽象泄漏法则,实际上大部分程序员(软件工程师)都还是需要从头到尾都关心。最近一段时间热炒的概念是“全栈程序员”。其实,一个程序员如果要解决问题,解决问题所要面对的技术都会被按需学习。每个职业的人都喜欢为自己的职业贴金,有很多程序员打心底里都认为编程是一种艺术而非技术,虽然他们日常开发更多依赖的其实是技术。
说了这么多,软件里的程序部分,目前还是需要人来做;软件的里的工程部分,也是需要人来做;这两者目前的情况属于半自动化。
大部分情况下,程序员首先要在个人技术和流程上有一个密集的阶段积累。无论是对编程语言的学习;还是在工程上依赖经验进行入门;但更重要的应该是反复的练习和实践。
我们再看下:
软件=程序+软件工程
单看程序,按下工程不表。下面的公式对么?
程序=算法+数据结构
这要看抽象的角度。侠义的说,算法和数据结构是非常Core的部分,一般只是占一个程序的Base部分。而一个Base部分,充其量只是一个程序的一个模块,即使是很核心的模块。
假设一个程序只用一种语言开发,一个语言包括运行时+基础类库。一个稍微有点规模的程序需要在语言运行时+语言基础类库的基础上,引入3rd(第三方)库,引入所需要的框架或者自己造,开发各种辅助支持库,并构建所要解决问题的领域模型,然后组织上层业务逻辑。
这些,中等规模粒度下,基本单元可以看成是一个模块。所以一个程序可以看作是
程序=语言运行时+基础类库+3rd库+框架+领域模型+业务模块。
而一个模块,在大部分语言下,由函数和类构成。在一个类的角度来看,可以考察的是类间结构和类内部结构。类间结构表现出来可能是设计模式,类内结构,可能才是数据结构+算法的领地,但也并非所有类都是数据结构,但他们都是一个有限状态机。
说了这么多,拆分到类,类下的基本行为单元还是函数。
所以,如果我们保证所有函数的行为正确,就能很大程度保证一个类的行为正确,从而保证一个模块的行为正确,而所有模块的正确最后就保证了一个程序的正确性。
怎样保证函数的正确性?我们知道语言有强类型和弱类型,强类型要做的事情就是通过类型的安全来保证程序的静态方面的某些正确性。据说Haskell语言的类型推导系统能使得一个Haskell程序的编译通过,基本上99%能保证程序的正确性。
但大部分OOP语言,类型的正确只是个底子。一个函数在运行时,依赖于该函数的假设条件是否满足,一个函数执行应该是怎样的状态,执行后应该是怎样的状态,一个健壮的函数代码可能需要写很多前置条件检查和后置状态检查。
但是检查的方式有两类,一类是运行时可能会出现的条件,但不算错误,此时需要在函数内部写条件判断语句检查,另一类是运行时调用该函数前后不应该出现的情况,此时需要做的是单元测试。从这个角度来说单元测试是类型安全的延伸,类型是一种对语义的限制,由于运行时语义的组合爆炸,导致不能通过语言内置的类型规则检查所有的语义限制,所以只好用单元测试来做补丁。
但是,单元测试又有两种类型:侵入型和外置型。所谓侵入型是指在函数内部做的各种Assert、Check,这些代码只会在Debug版或者日志版有效,在发布版里不会出现。而外置型则是指为一个模块单独建立测试工程,在测试工程里为每个函数每个类专门编写测试函数,测试函数和类的各种可能的调用(测试用例),只有这些测试都通过了,这个模块的健壮性才得到一定程度保证。
但是,正如构建之法书中所言,100%的单元测试覆盖也不能保证消灭所有BUG。譬如我自己日常开发里,最麻烦的莫过于多线程的BUG调试,由于很多边界条件依赖于线程的执行序才能重现,这部分很多时候都需要靠人肉分析,定位,甚至有时只能靠细致的code review,但一般来说如果能重现,那就离解决不远。
而修复了BUG后,还需要做回归测试。以前有人说:修复一个BUG引入了3个新BUG,最后一个BUG修复后才把最开始的那个BUG修复,所以回归测试是一种BUG Review。
以上两部分是根据这章首先引入的两个部分,接着是性能分析。有一句话说过早的优化是一切罪恶的根源。而优化往往是需要以数据说话的,使用性能优化工具查找性能热点,然后再有的放矢的去优化。我觉的不过早优化也有利于工程,早期应该关注代码的良好设计(譬如后面结对编程里会引入的编程规范、代码风格等),以及代码的层次设计(无论是类间还是模块间)等。
## PSP(Personal Software Process)
我很喜欢PSP这个概念,特别是Process。一个人的技能是静态的,但Process强调过程性,我们的日常开发也是过程性的。在一个开发过程中,会产生很多指标,这些指标可以用来做自我衡量。当然,在没有工具支持的情况下,需要这么做的程序员拥有良好的做记录的习惯,我想大部分程序员不会每天给自己做PSP指标记录,如果是实验室里的科学家,倒是有可能每天对自己喂养的小白鼠做个种定时记录。但科学告诉我们,准确和记录产生的数据会说话。所以,也许在一定粒度上定期自己Review自己的PSP数据的,也应该是一个不错的方式。当然,牛逼的程序员是不需要靠间断自我采样来提高的,大部分程序都宣称自己是牛逼的,所以PSP的真实被显性使用应该很少。
过程,意味着不是一个步骤就完成的。我认为此处依然是边界识别问题。我们做一件事,有很多个步骤,我们可能高度耦合(非线性)在一起就做了。但拆开的话,应该会有好几个过程,我认为有效的方式未必要刻意把这些过程拆开去做,但一定要有边界识别的感知能力。我做,我能理解我此时时刻做的是属于某个过程的东西,我再做,时间上虽然是连续的,但它属于某个其他过程。
## 基本、高级和扩展
wc程序的基本需求、高级需求、扩展需求。每个基本需求都要做么?是的!每个高级需求都要做么?每个扩展需求都要做么?我相信很多学生对为什么要思考和提出高级需求、扩展需求是模糊的。我的看法是,高级需求和扩展需求,是为了后续的迭代。一个软件,会有多个不同阶段的开发周期,譬如一周一次迭代。那么基本、高级、扩展需求是一个初步指南,每个开发周期不宜过长,但在一个迭代周期内要尽量做到按时高质完成对应要做的功能(也就是需求里所提出的)。这样,这个软件的整个长期开放周期会有一个后劲,当然,需求的变动是正常的,在迭代周期的过程中,根据用户反馈、开发进度调整需求也是要应对的。
参考幻灰龙的读书笔记