`
ext2xhb
  • 浏览: 9586 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

程序员新人导师

阅读更多
题外话:程序员的力量之源

程序员在运用技术去改造影响着现实,或者被现实的需要不断推动向前行的路程中,大多数人都相信其力量的源头中最值得信赖的伙伴是“抽象”。
当面对问题域已知的部分,依赖抽象,我们可以把繁杂的片断持续的分类,简练概念,并作为从问题域到实现域的依据。抽象结果越简单,越清晰,实现域的方案越简洁,越明了。
当面对问题域未知的部分,借助以往抽象认识的经验去模拟体验,演绎得到对未知问题的各个侧面的认识,然后再次借助抽象去提炼,分类。


1. 当老人遇到新人
1.1. 核心——定义“基本方法”

不用多说,每个引导者都想把“方法”传递给新人。所以引导者需要考虑一下,对自己认为的最本质的方法下个定义。

对于程序员这个职业,其能力中最重要的就是“抽象”。如何在软件这个领域理解和运用“抽象”,就需要在“引导”过程中指导。或者如果认为别的因素是最重要的,那么他应当对此总结,并引导新手去运用。

准则——可验证吗?

“基本方法”都是高度抽象的。在“引导”新手的过程中,每个步骤必须给新手提出清晰的准则:对于准则,要求可被新人直接用来对类似行为进行自我检验;

引导者也必须对准则本身进行解释——制定的准则与“基本方法”之间的关联,可被新人用来体味。

所有的指导行为不给出准则,而直接上升到“基本方法”这个高度,就只能是空对空。


例:编码时变量正确命名问题,我们可以解释为:“编码应当基于相对抽象的概念和逻辑,所以每个组成部分都应当反映一个明确的概念或者与之相关的逻辑。而变量往往与概念关联,所以我们应当使用概念的定义来命名变量”,但这个解释对新人来说是无法自我检验的。
所以更好的做法是给出“使用名词化方式变量命名”的基本准则,并使用上面的话来解释制定这一准则的原因。

犯错误的程序——验证方法与准则的时机

当新人犯错误时,纠正时需给出准则要求,向新人展示你改进的过程是如何使用提出的准则的。引导者在修正问题时尽可能采用渐进演变的方式——对于编程工作,一个好,坏方案差距可能非常大,我们需要逐步演变的方式,揭示每一步变化的理由,以辅助理解。

最后对提出的“准则”给出相对一般意义下的解释(方法),来揭示准则与“基本方法”的关联关系。

1.2. 机会主义——习惯最重要,知识不完全必要


好为人师者不要真的把自己当作老师。引导者绝对不能把自己认为正确的认识都灌输给新人,只能根据工作情况的需要来灌输。因为教给新人的准则,观念,和做法都是作为“核心业务能力”的引子传递给新人的。让新人明白准则背后的普遍概念,并帮助新人建立正确的做事习惯,新人在后面的工作中自然会自己总结和认识。


1.3. 屁股与脑袋——小心,“岗位不同,认为理所当然的东西也不同,不要把认知范围的差异做为批评的理由”

人们对同一个问题所要求的外延理解可能不同。这种不同有时是因为能力的差异,有时是因为岗位职责不同的所决定的视野差异所导致。引导者有责任辨识出:因为视野差异导致新人对问题的观察不充分,而发生错误的情况,并明确的告诉新人,以保护新人不断增长的独立分析问题的信心与其个人思维模式的稳定的过程。

需要注意的是这种视野差异有时候是很难判断的,因为人们在传递问题给其他人时,往往传递的是对问题的分析结果,而相关的外延却被判断为无关而被无意识的忽略。所以引导者更要谨慎,随时观察是否因为自己的因素导致新人的视野观测不全。


例:
假设我们需要为系统中增加一个模块实现配置功能。为此定义了所有的配置参数的细节。规定以 xml 方式存储,以及存储位置的保存方式。同时要求配置读取模块加载的参数在内存中表示为一些普通的对象 , 通过对对象的属性的访问可以读取配置,通过对对象参数的修改,可以保存配置。

一个错误地实现方式:“系统运行所需要访问的配置对象实例”与“用于配置修改的配置对象实例”没有分开——导致的后果表现为:修改并保存配置后,程序的运行系统所访问的配置对象属性变为修改后的值,这样导致一旦在线配置,可能导致运行系统的某些功能出错。

导致该错误的原因就在于忽略了一件事情——动态配置修改运行对象的值,往往是危险的,需要根据该运行对象的实现机制加以控制. 但这样一个事实对于一个负责实现配置模块的新手而言可能是超出其观察范围之外的.

1.4. 直觉——呵护新手的直觉,哪怕不准确

解决问题时人们从来不缺乏“貌似有道理的想法,或者貌似不太对的感觉”。不同人的差别在于对待这种感觉的态度:简单的忽略它,按照既定的要求去做。还是把它当作一个问题,分析导致感觉产生的背后原因并试图解决。
有意思的是这些直觉最重要的意义在于:它们意味着人们可能对要解决的问题认识不清,或者解决问题的手段而引入的问题认识不清。所以不同的对待直觉态度和工作习惯导致有些人总是亦步亦趋,而另外一些人总是以自己的方式主导事情的进展。

因此保护新人的直觉将是引导者最重要的一部分工作内容:要求新人不可忽略自己的感觉并要求其尝试分析直觉背后的原因;协助新人,将其无法分析清晰的感觉分析清晰并给出建议,以解释直觉对其工作的重大意义。即使是不正确的直觉,也往往能够反映新手自身的缺陷,分析清楚这类直觉的产生原因,可帮助新人认识自己不足之处。

2. 程序员的世界
2.1. 位面——问题域与实施域

问题域与实现域的关系大概是新人在完成第一个可称为子系统的程序后,最迫切需要和引导者一起分析并探讨的问题了。虽然此时遇到的问题域未必是真实的业务需求,但是不管怎样,此时的讨论总有类似的背景。

遗憾的是,这里的准则总是无法自我检验的。引领者唯一能做的是通过新人亲身经历的实例来告诉新人:“好的,这些要求是很重要的,以后考虑做事情时要养成先考虑这些要求的习惯”。

关于问题域应该给新人的一个交待:不单指业务要求,也包括自己所做那部分内容相关的其他系统 / 部件的要求:有关联的其他模块,实现所必须依赖的某种技术的要求等等。

准则:问题域优先于实施域,对问题域的抽象要优于对技术实施的抽象。

习惯:问题域出现繁琐的要求的时候,有没有尝试对它们进行统一。有些时候我们可以使用分类的方式进行,而另外一些时候当没有办法直接分类统一的时候,尝试变换一下(强化)要求也许是个好的主意。

例:一个业务系统的人机交互部分,按照的业务要求为不同的功能设计了很多的对话框界面。

问题域中的各种功能所要求的对话框,就是一个繁琐的东西:我们得一次又一次编写代码来创建界面,这实在是太令人厌恶了。
那么换个说法“一个根据业务要求可定制的界面”可能会好一点。
好的我们得承认,这个说法在实现的时候是很困难的,因为业务逻辑和界面元素的关联性我们没有很好办法处理。但是不管怎么样,如果在实现领域引入“界面描述”使得界面可以“根据实现的要求可定制”,而不用每次都编码。至少可以解决界面代码编写的繁琐。

可是当我们有了界面模版的时候,依然有件罗嗦的事情:我们得一次又一次的从界面元素中提取用户录入数据,然后把它复值给程序内的逻辑处理对象。

回到原来的说法,“一个根据业务要求可定制的界面”,意味着,“业务所处理的信息是可以定义的”。
借助“元数据模型”,让实现者根据业务要求定义元数据是也许是可行的。只要定义元数据的过程比直接编码简单 ; 只要元数据可以使用工具转化为程序中使用的逻辑对象 ; 只要处理描述性界面的程序可以理解元数据的定义以及元数据的逻辑对象,做到从界面元素提取数据并赋值给对应字段,以及从对应字段取值在界面显示;恩,这样我们就不用每次啰嗦的提取界面数据了。


准则:问题域的理解是基于对使用者以及要求者的模拟体验的。

习惯:觉得问题的描述不太清楚的时候,有没有站在使用者的角度去模拟一下他们如何使用,来帮助对问题域的理解,有没有站在要求者的角度考虑一下为什么会提出这些要求,还有可能提出哪些要求?
(使用者不限定为是业务系统的使用者,逻辑是分层的,问题域也是分层的,一个系统,部件的问题域的分析除了相关的业务使用者,也面临和他交互的其他系统,部件。)


例:为程序增加一个 xml 的配置文件,配置运行所需要的参数。
是的,开始的时候并不太清楚具体写成什么样子。
不过考虑一下所写的配置读取程序的使用者:程序中其他的需要读取配置的子系统的编写者——从他们使用方便的角度,可能希望我的配置读取模块提供给他们的是对象,而不是一个 xml 树,这样方便他们的对参数的访问。
考虑一下对配置读取程序的要求:配置的参数都是来自其他运行系统所要求的参数。是不是所有的运行系统配置参数的格式都可以预定义。如果是,这些预定义的参数都可以转化为对象的属性值。但是如果不是意味着某些配置需要保持 xml 树的格式保存在特定属性中,然后运行系统自己去枚举 xml 树。

考虑一下配置文件的使用者:可能存在人手工配置的情况。为了简化配置,也为了回避因为疏忽而遗漏配置的情况,是不是需要为读取的参数规定默认值。
由于使用了默认值,考虑对配置读取程序的要求:是不是运行系统要求有些配置参数必须不能提供默认值?
。。。

准则:好习惯,但别太过分——过度设计,过度分析。


2.2. 阶段


程序员的成长必然是分阶段的,不同阶段的需要也不同。如果有个初步的认识,对于个人的成长总是有益的——剖析自我,了解当前的需要,与下一步的需要。对于引导者来说,有意识与新人多交流与探讨相对应的阶段所应该了解的基本概念,也是有益的——避免概念泛滥,而难以接受。

程序员大抵分为 3 个阶段吧,虽然未必准确。

入门:学会问题到实现的映射,辨识程序的好坏味道,以及一些简单朴素的准则
成长:把实现领域的共性问题作为认知考察的分析对象,提炼和分析常用准则和方法
进阶:把如何正确的“分析业务”作为认知考察的分析对象,提炼和分析常用准则和方法

2.2.1. 入门:学会问题到实现的映射

通过一些途径,训练新手建立“通过模拟实现以辅助对问题的理解”的思考习惯。

准则:程序中的每个需要命名的构成要素,其名称必然可以使用有意义的方式命名。
辅助新手逐步形成“编程应当基于抽象的概念与逻辑——无论是宏观架构还是微观实现”这一朴素的感受。

习惯:尽量在变量,函数与模块在实现展开之前,先考虑一个自己认为最合适的名字,而不是在内部实现完成之后 .
习惯:在头脑中预演实现的细节,并尝试只借助纸笔进行口头表述。有些细节出现记忆障碍了么?尝试定义一个合适的称呼来代替它们。
习惯:在实现完成后,闭上眼睛在头脑中重现。有些细节出现记忆障碍了么,或者有皱眉头的感觉?可能需要提炼与之相关概念或重新分解

辅助新手逐步形成“抽象的概念是分层的,他来自对原始问题域的逐步分解”这一朴素的感受。

培养这个习惯时要特别警惕新人一个误区:

误区:不要为了实现而实现。——预演实现的目的是为了辅助对问题域的理解,验证实现的可能性,而不是为最终实现提供依据。(模拟实现可以对问题域的关键问题与细节进行验证,并且可以发掘出其他的关键细节),最终实现要依赖于对理解后的问题域的重新抽象(对已知的确定的细节,重新分类,定义。作为最终实现的依据)

遗憾的是关于误区的提醒,无法自我验证,所以新人很难回避这个误区。好在存在容易检测的标准,帮助新人检验自己是否已经陷入了这个误区。当新人一次次发现自己陷入误区,然后修正的过程中,就可以逐渐掌握自己的方式来做到事前回避。而这些检测标准就是关于程序好坏味道的一些验证标准。

例:前提到的业务系统的例子中,我们对业务系统得每个业务要求进行如何预演,考虑使用者怎么和最终的实现系统交互,系统后续步骤如何处理完成业务要求。
这些简单的预演得到了大量的人机交互界面。如果直接基于这样一个结果实现,难免的会引诱新手对每个界面单独编程。而在此基础上,对繁琐的人机交互这一事实重新提炼我们可以得到“可订制界面”的解决办法。



通过辨别程序的好坏味道,可以帮助新人识别“自己是否已经陷入为了实现而实现的误区”。
至于坏味道,每位成熟的程序员都会有自己的认识和判断,根据情况教导新人好了。但是重复与耦合,是务必要引导新人有所认识和理解的,尤其是“重复”。

重复:

实现的重复——重复的代码出现多次。
需要引导的准则:
       封装的原则——重新提炼概念,并封装
       基于封装的复用的原则
结构的重复——类似的行为重复多次。
需要引导的准则:
       基于分类的抽象原则:对象,继承,虚函数等。
       基于抽象实现的复用:抽象类技术,表驱动,接口指针等。
     
任务的重复——某种技术手段的反复使用。
困难,需要转换概念,转换后的概念实现可能依赖已经存在的工具或者自己编写的工具。
这种现象对新人描述一下蓝图就好了,因为解决这个问题实在不是一件容易的事情。但是有些兴趣强烈的新人会依据描述自己去探索和理解。因此描述一下蓝图就会给新人一个机会不是么?

譬如:
界面编程:反复的界面元素与布局编程——界面的描述性布局定义(依赖可视化工具)
游戏编程:不断变化的任务系统—— > 命令模式(命令的结果引发人物的动作或界面的变化。人物的动作结束,以及界面的元素的点击会触发新的命令)

耦合:复杂的耦合关系(双向耦合)与考验大脑记忆的偶合(顺序耦合)


需要新人了解的朴素概念:
在前面的描述中已经提到了这里简单罗列一下 . 所有的朴素概念无法直接给新人描述,只能通过实际遇到的情况,以及一些可让新人自我验证的简单准则来解释。让新人明白这些朴素的概念是如何运用的。

准则:问题域优先于实施域,对问题域的抽象要优于对技术实施的抽象。
准则:问题域的理解是基于对使用者以及要求者的模拟体验的
准则:依赖抽象的概念和逻辑进行编程,而不是编码实现的具体需要
准则:通过预演实现来确定问题域的可行性,发掘未察觉的重要细节。
准则:实现时不可把预演得到的实现为最终依据,避免为了实现而实现。实现应当基于抽象
准则:遇到不可回避的复杂 / 繁琐实现时,尝试在问题域中变更概念
准则:习惯于将概念的实现封装起来,即使它只被使用一次
准则:尽可能的消除重复
准则:避免双向耦合,双向耦合出现时优先考虑引入中间概念。


2.2.2. 成长:

相对成熟的程序员会有意识的思考,学习关于如何实现的一些准则和方法。原因在于频繁的实现活动会自然而然的导致程序员把“如何实现”作为一个专门的问题来研究。作为引导者必须有责任向新人展示来自属于这阶段的某些准则和设计方法的使用的综合考虑,这么做的原因很简单,为新人提供蓝图,给有欲望的新人打开快速进入的门。
例:设计模式中的 factory 模式,对于新人来说,可以是为了避免不必要的耦合(构造的细节),的一种实用手法。但是实际应用中可能涉及的外延很多:比如在程序中如何使用 factory 以及接口,完全屏蔽调用者与被调用者的关联性。如何依赖某种部署描述的方式结合 factory 模式,来实现动态组装处理的办法等等。

2.2.3. 进阶:。


这个阶段把如何正确的“分析业务”作为认知考察的分析对象,提炼和分析常用准则和方法。太过抽象,根本是放在一般意义上来解决问题的阶段。不过我们可以传递给新人的就是:在程序员的技术实践活动中所沿袭的准则实际上就是放在“普遍环境下如何解决问题”的准则,程序员的能力归根到底是一般意义上如何正确做事的能力。因此我们无需因为自己掌握的技术和手段而自得,因为它根本上只能帮助我们“观察问题”,而无助于“解决问题”。但另外一方面,程序员总是更容易获得“一般意义上正确做事”思维习惯,不是么?因为我们总是持续的如此训练自己。

3:“开发者笔记”的工具

开发者笔记:引导新人的有用工具,需要新人记录引导者认为的典型问题,以及自己在实现活动中认为有意义的行为。

基本内容包括 3 部分:

1. 问题的简要描述(以后看的时候可以回忆起当时的情景)
2. 自己认为的实现方式 对比   引导者协助修正后的实现方式(最好是源代码或图表)
3. 每个变更给出一个理由。描述理由时候,严禁使用准则来解释变更理由

譬如:“为了防止 A 模块和 B 模块的耦合”这样一个理由就是严格禁止的。

鼓励使用假设场景的方式解释理由:
譬如假设增加什么样的功能,并对功能进行描述,然后解释 A 实现方式下增加该功能的麻烦,以及 B 实现方式下的便利。

鼓励使用抽象逻辑描述的方式描述需求:
譬如“我们应当允许用户根据自己的应用逻辑,定义与应用相关的异常,同时出于底层运行系统的需要,会引发一些与系统运行状态相关而与应用无关的异常。所以我们的异常的设计分为了两类异常”


分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics