糟糕的、差劲的,以及不该来当程序员的程序员

fmms 13年前
     <p> <strong>缺乏根据代码推导的能力</strong></p>    <p> 根据代码推导意味着能够跟踪执行路径(“在脑子里运行程序”),并且明白代码的目标是什么。</p>    <p> <strong>症状表现</strong></p>    <ol>     <li>存在“莫名代码”,或是对程序目标毫无成效,但却在拼命维护的代码(比如,初始化了却从未使用的变量,调用了和目标无关的函数,产生了未被使用的输出等等)</li>     <li>将等效函数多次执行(例如:多次调用 save ()函数,“只是为了确定真的保存了”)</li>     <li>通过撰写多余代码覆盖掉出错代码的结果,来修复缺陷</li>     <li>“车轱辘话代码”(yoyo code),即将一个值转换成一种不同的表示,然后又把它转换成原始的样子。(例如:将一个十进制数转换成一个字符串,又转回一个十进制数,或用空白填充一个字符串,又对它做空白修剪操作)</li>     <li>“推土机式代码”(bulldozer code),表面上看是把代码块打散成若干子程序的重构动作,但是这些打散后的若干子程序却完全不可能在另一种环境中复用(因为它们之间有很高的耦合,不能分开使用)</li>    </ol>    <p> <strong>补救措施</strong></p>    <p> 为了克服这方面的不足,程序可以采用 IDE 自带的调试器作为辅助,如果该调试器提供了单行步进能力的话。例如,在 Visual Studio 中,就可以在出问题的代码区块的开头设置一个断点,并通过按“F11”键单行步进,并检查变量值的前后变化——直到你理解代码是要做什么事为止。如果目标环境中没有调试器这种功能特性,那就找一个有这种特性的来练手。</p>    <p> 我们的目标是达到这么一个状态,你可以不再需要调试器就可以在脑子里来跟踪代码走向,并且你有了足够的耐心来根据程序状态来思考代码在做什么。回报是识别冗余和无用代码的能力,以及从已有代码中发现缺陷,并且不必从头重新实现一遍整套算法。</p>    <p> <strong>未能透彻理解语言的程序设计模型</strong></p>    <p> 面向对象的程序设计是一个语言模型的例子,其他的还有函数式或声明式程序它们中的每一个都与面向过程的或是命令式的程序设计有着显著的不同,正如面向过程的程序设计与汇编或是基于 GOTO 的程序设计有着显著的不同一样。还有一些语言,它们从属于一种主要的程序设计模型(比如面向对象的程序设计),但是同时也引入了一些它们提供的改进,比如列表解析、泛型、鸭型型别(<strong>译注:</strong>即用相同的接口和大部分输出响应来模拟某种型别,实际上有所不同的型别,用语取自谚语“如果它叫唤时像只鸭子、吃食时像只鸭子、连跛脚都像只鸭子,那它就是只鸭子”)等等。</p>    <p> <strong>症状表现</strong></p>    <ol>     <li>不择手段地使用各种语法来打破当前使用的模型,尔后采用命令式/过程式风格来书写余下的程序</li>     <li>(面向对象)试图调用未实例化类中的非静态函数和变量,并且难以理解为何通不过编译(译注:即分不清类和对象)</li>     <li>(面向对象)写一大堆“xxxxxManager”类,里面包含所有操作类的方法,但这些类却没有自己的方法。(<strong>译注:</strong>即仍然把类看作 struct,未能掌握用类自己的方法——C++中叫做成员函数——来操作类数据的新模型)</li>     <li>(关系型)将数据库当作一个对象存储,在客户代码中完成所有的连接和关系模塑</li>     <li>(函数式)为相同的算法创建多个函数版本来操作不同型别和操作数,而非将高层抽象的函数传递给一个泛化的实现</li>     <li>(函数式)手动缓存确定性函数(译注:即对于同样输入返回同样结果的函数)的返回结果,即使平台会自动完成这件事(例如 SQL 和 Haskell)</li>     <li>(纯函数式)使用从别人程序里复制-粘贴的代码来处理与I/O和单子</li>     <li>(声明式)采用命令式代码逐个地设置变量的值,而不使用数据绑定</li>    </ol>    <p> <strong>补救措施</strong></p>    <p> 如果你的技能缺乏是无效教学或研究的产物,那么另一个老师就是编译器本身。要学习一种新的程序设计模型,在效果方面无与伦比的方法就是启动一个新工程并将自己投入在应用那些全新结构上,别管它们是什么,也别管看上去傻不傻。你还需要练习使用你熟悉的一切来解释该模型的特性,用语不妨粗糙一些,然后不断重复地构造你的新词汇表,直到你同样掌握了新特性的精妙之处为止。例如:</p>    <ul>     <li>第1阶段:“面向对象就是带有方法的记录型别”(译注:记录即C-style struct)</li>     <li>第2阶段:“面向对象中的方法,就是在一个小程序里运行的函数,这个小程序带有自己专属的全局变量”</li>     <li>第3阶段:“这些全局变量被称为域,其中有些带有私有访问层级,在这个小程序之外是看不到它们的”</li>     <li>第4阶段:“为元素搞私有和公有访问层级的名堂,这个思想是要隐藏实现细节,并且只暴露一个干净的接口,即所谓封装”</li>     <li>第5阶段:“封装意味着我的业务逻辑不必被实现细节所污染”</li>    </ul>    <p> 第5个阶段看起来对所有语言都适用,因为它们的确是想要程序员理解到这一步,这样他们就可以表述程序的目的,而不会将它埋藏在如何实现的具体做法中。再举函数式程序设计作为另一例:</p>    <ul>     <li>第1阶段:“函数式程序设计就是把确定性函数链接在一起”</li>     <li>第2阶段:“当所有的函数都成了确定性的,它们在要求输入结果之前不必被执行,并且只须需要多少结果就执行多少部分即可。即所- 谓缓式评估求值和部分评估求值”</li>     <li>第3阶段:“为了支持缓式评估求值和部分评估求值,编译器要求我以对单个参数做变换的形式撰写函数,有时变换的结果是另一个函数。即所谓柯里化”</li>     <li>第4阶段:“当所有的函数都完成了柯里化后,编译器就可以运用一个约束求解器来决定最佳执行计划了”</li>     <li>第5阶段:“通过让一个约束求解器来决定机器细节,我撰写程序时就可以只描述我想要什么结果,而非怎样得到结果了”      <div id="come_from">       <br />       <br /> 来自:       <a id="link_source2" href="/misc/goto?guid=4958198261756650202" target="_blank">www.ituring.com.cn</a>      </div> </li>    </ul>