Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

从设计到“以人为本”的程序设计 #2

Open
yuchong-pan opened this issue Jul 21, 2019 · 0 comments

Comments

@yuchong-pan
Copy link
Owner

commented Jul 21, 2019

我很喜欢 Donald Knuth 的一句话:“一个程序员,如果潜意识里把自己看作一个艺术家,就会热爱他的工作,并且做得更好。”学习计算机编程的时间越长,我就越体会到这句话的分量。初学计算机编程时,我们总是以为学会了编程语言的语法就学会了计算机编程。逐渐地,我们意识到学会用计算机编程解决问题是一种更重要的能力。而现在,我认为计算机编程的核心不在于“代码”本身,而在于设计代码的过程。计算机编程是一种设计、一种艺术。因此,我常常把自己看作一个程序的设计师。

DonaldKnuth

“赏心悦目的飞机,才能够飞得平稳舒适。(For an aircraft to fly well, it must be beautiful.)”这是法国飞机设计师、航空工业家 Marcel Dassault 的一句名言。这是因为“赏心悦目”意味着飞机具有良好的气动外形,使得飞机更符合空气动力学。这句话应用到计算机编程中同样适用——赏心悦目的程序,才能够最小化出现错误的可能性,使程序更具扩展性和可维护性,并且便利团队协作。这是因为“赏心悦目”意味着每一部分的代码都分工清晰明确,不同部分的代码之间耦合(coupling)、依赖(dependency)较小。因此,改动代码时只需要专注于某一部分代码的改动,而不需要来回改动整个代码库以至于出现“牵一发而动全身”的情况。同时,“赏心悦目”的代码本身就是 “self-documenting” 的;代码的结构就很好地说明了每一部分代码的功能和设计初衷,因而极大地便利了团队成员间的沟通。

什么样的程序设计是好的设计?我想从设计本身入手。根据这篇文章,认知学家 Don Norman 认为好的设计应当具有“可发现性(discoverability)”和“理解力(understanding)”。其中,“可发现性”决定了用户能否发现可以进行的操作,并且了解产品的状态;“理解力”指用户可以分辨出产品的期望目的和意义。这两条原则同样可以适用于程序设计中。在通常的设计中,产品的用户即指使用产品的人;而在软件工程中,用户由两部分组成——使用程序的人和其他程序员。前一类用户决定了产品功能和用户界面的设计,这部分设计通常由产品经理和设计师来完成;后一类用户决定了程序本身的设计,这部分设计应当由程序员来完成。不幸的是,很多程序员都忽视了他们的同行的需求。

对于程序员来说,“可发现性”指程序员能够通过程序的结构清晰地分辨出如何扩展程序;同时,程序的设计应当能够在某种程度上阻止不合法的扩展。在软件工程中,我们应当认为人总是会犯错的。因此,我们需要一套自动执行的机制来阻止人犯错误,而非一套由人来执行的检查确认程序。毕竟,谁能保证人在检查确认的时候也不犯错呢?一个常见的方法是使用类型系统(type system)来维护程序的规范性。结合现代化的 IDE,静态类型系统能够提示程序员赋值和函数调用时的变量/参数类型,并且在编译时阻止类型错误的发生。一个更复杂的例子是面向对象程序设计(object-oriented design, OOD)。程序员在进行面向对象程序设计时,应当思考如何设计类型继承关系(type inheritance)和每一个变量/参数的类型,使得程序既能够引导程序员进行合法的程序扩展,又能合理阻止不合法的扩展。从这个角度讲,C# 比 Java 提供了更多面向对象的特性使得面向对象程序设计更加灵活、更符合“可发现性”。例如,C# 的 sealed 关键字限制了某些类(class)不可被继承,而 override / abstract / virtual 关键字规定了可被重写(override)的方法(method)。

另一个原则“理解力”则是指程序的结构和语义能否帮助其他程序员理解程序每一部分的分工和设计初衷。很多时候,软件开发的难点在于理解一个庞大的已有代码库,特别是在团队协作的环境中。软件工程的课程通常告诉学生在设计软件前以注释的方式写下每一个函数的描述和目的。这些注释固然能够帮助其他程序员理解程序的含义,但我认为,一份好的代码应当是能够通过代码本身(除注释外)来解释代码的含义,即是 “self-documenting” 的。一方面,注释很难覆盖到代码的方方面面。如果注释和代码的比例过大,那么程序的开发效率必然会受到影响。因此我们通常只会给每一个函数加上注释,这样函数内的“微观”结构,比如各种控制结构(分支、循环、异常处理等),便不能通过注释来解释语义。另一方面,注释是非强制性的,即存在注释与代码完全不相关的可能性。如果依靠注释来解释代码的语义,那么我们事实上是把代码和注释耦合了,即更改代码会要求我们更改注释。这样既不符合软件开发的原则,也大大增加了误导程序员的可能性。因此,程序员在写代码时应当注意代码的结构——无论是“宏观”结构(如函数、类、命名空间等)还是“微观”结构(如函数内的控制结构等)——都应该做到程序员能够通过代码本身推断出代码含义。我常看到一些程序员非常喜欢“炫技”,使用一些不必要的技巧来实现一些可以用更清晰的方法来实现的功能。有些人甚至喜欢使用一些未定义行为来写程序,如将 C++ 中的自增操作符 i++ / ++i 叠加使用。这些癖好事实上是非常不可取的,因为他们使得程序的语义变得模糊不清,从而影响了团队共同协作的能力——而现代的软件工程通常是在团队的环境下进行的。感兴趣的读者可以阅读这篇文章来了解程序语言中一些使语义模糊的设计。

和“以人为本”的设计理念一样,程序员在进行程序设计时也需要考虑到程序的各个阶段中涉及到的“人”的因素。上文中,我提及了两个“人”的因素——程序的使用者和其他程序员。事实上,我们还可以发现更多与“人”有关的因素。例如,在现代的软件工程协作中,我们需要考虑如何将程序的用户相关代码与底层核心代码分离,最小化面对来自产品经理和设计师的需求改动时的代码改动量。又例如,我们需要考虑如何设计代码的结构和访问控制(visibility),在保证程序自动纠错机制的同时,方便程序的测试者对程序进行测试(这里的测试者也可能是程序员自己)。综上所述,我们可以看到,与其说程序设计是一门写代码的艺术,不如说程序设计是一门与人打交道的艺术。因此,如何衡量各方面“人”的因素,如何在各个因素的冲突之间进行妥协和让步并最终找到一种平衡,是程序设计中值得思考的一个问题。


如果你觉得这篇文章对你有启发,可以点击 #3 付费。

@yuchong-pan yuchong-pan added the 探索 label Jul 21, 2019

@yuchong-pan yuchong-pan self-assigned this Jul 21, 2019

@yuchong-pan yuchong-pan changed the title 从设计到程序设计 从设计到“以人为本”的程序设计 Jul 31, 2019

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
1 participant
You can’t perform that action at this time.