代码的艺术
本门课程为《代码的艺术》将从《代码的艺术》目的解读、代码与艺术之间的关系、软件工程师不等于码农、正确认识代码实践方面的问题、怎样修炼成为优秀的软件工程师六个方面进行解读。
大家快准本好小本本听讲吧~
《代码的艺术》目的解读
了解公司与学校写代码的不同
在公司写程序和在学校写程序有很大的不同。
在学校写程序时,对于代码的质量要求比较低。
当进入公司之后,做的是工业级的产品,服务用户量可能会到达亿万级,所以相对而言对于代码的质量要求比较高。一些伟大产品中的代码,甚至可以被称为艺术品。
消除对于程序员这个职业的误解
很多人都对程序员这个职业有误解,认为程序员就是码农,认为程序员 35 岁之后就写不出代码了。还有人认为程序员未来的唯一出路是以后做管理。
希望通过这门课程的学习,能使大家对于程序员有一个新的认识,消除误解。
建立对软件编程的正确认识
在做一件事物时,我们常说“知”与“行”要合一。即:我们需要对这件事物有一个正确的认识,才会有正确的行动。同理,写出好代码的前提,是对软件编程有正确的认识。
明确作为软件工程师的修炼方向
艺术品是由艺术家创造的。艺术家的修炼是有方式方法的。同样,软件工程师的修炼也是方式有方法的。希望通过这门课程,能使大家对软件工程师这个职业有一个全新的认识。
代码与艺术之间的关系
代码是可以被称为艺术的
艺术,是多种多样、丰富多彩的。同时艺术也是有多个层次的,其实,在我们编写代码时,我们的脑海中也会有类似的感觉。
艺术就是人类通过借助特殊的物质材料与工具,运用一定的审美能力和技巧,在精神与物质材料、心灵与审美对象的相互作用下,进行的充满激情与活力的创造性劳动,可以说它是一种精神文化的创造行为,是人的意识形态和生产形态的有机结合体。
写代码也恰恰要经历这样的一个过程。
在编写代码的过程中:
我们借助的物质是计算机系统,借助的工具是设计、编写、编译、调试、测试等。
同样,编写代码需要激情。而且,编写代码是一件非常具有创造性的工作。
代码是人类智慧的结晶,代码反映了一个团队或一个人的精神。代码可以被称为是艺术的。
艺术可以从不同的角度进行解读、研究与创造
达芬奇有多幅著名的画作。拿著名的《蒙娜丽莎》这幅画来举例:
![](https://static001.geekbang.org/infoq/48/482e460bb359c02e002a16dacca55f4a.webp?x-oss-process=image/resize,p_80/format,jpg)
站在观众的角度,可能只是在欣赏画中的人物微笑。但是对于画家来说,可能就会考虑画画的手法、构图、光线明暗、色彩对比等等方面。
在艺术方面,可以站在很多不同的角度进行解读。
但是如果要成为一名创作者,我们需要的不仅仅是欣赏的能力,更重要的是从多角度进行解读、研究与创造的能力。
写代码如同艺术创作
写代码就如同艺术创作一般,并不是一件容易的事情。
写代码的内涵是:
①写代码这个过程是一个从无序到有序的过程。
②写代码需要把现实问题转化为数学模型。在写代码的过程中,我们需要有很好的模型能力。
③写代码实际是一个认识的过程。很多时候,编码的过程也是我们认识未知问题的过程。
④在写代码的过程中,我们需要综合的全方位的能力。包括把握问题的能力、建立模型的能力、沟通协助的能力、编码执行的能力等等。
⑤在写好代码之前,首先需要建立品位。品味是指我们首先要知道什么是好的代码,什么是不好的代码。这样我们才能去不断地调整自己的行为,然后去学习,去提高我们的编码能力,写出具有艺术感的代码。
软件工程师不等于码农
软件工程师不能只会写代码,更需要具有综合的素质。这个综合的素质包括:
1、技术
技术能力是基础。包括但不限于编码能力、数据结构和算法能力、系统结构知识、操作系统知识、计算机网络知识、分布式系统知识等等。
2、产品
要对产品业务有深刻的理解,需要了解产品交互设计、产品数据统计、产品业务运营等。
3、其他
要了解一些管理知识,需要知道项目是怎么管理的 ,如何去协调多个人一起去完成一个项目。有一些项目需要具有很强的研究与创新方面的能力。
以上这些能力素质,是一个软件工程师需要具有的综合素质。要成为一个全部掌握这些素质系统工程师至少需要 8~10 年的时间。
所以,软件工程师绝对不是一个只会简单编写代码就可以的职业。软件工程师不等于码农。
正确认识代码实践方面的问题
什么是好代码,好的代码有哪些标准
好代码的标准是:①高效、②鲁棒、③简洁、④简短、⑤可共享、⑥可测试、⑦可移植。、⑧可监控、⑨可运维、⑩可扩展。
将以上十条标准进行总结精简,可以归纳为:
(1)代码的正确和性能;
(2)代码的可读和可维护性;
(3)代码的可运维和可运营;
(4)代码的可共享和可重用。
了解完好代码的标准,接下来我们来看一下不好的代码主要表现在哪些方面:
(1)不好的函数名
比如,在函数名中,加 my 等单词,这属于很不专业的用法。
(2)不好的变量名
比如,看不出任何含义的 a,b,c,j,k,temp 等变量名。
(3)没有注释或注释不清晰
没有注释的代码是非常难读懂的。注释不清晰往往是因为文字功底或者描述能力欠缺,从而导致无法通过注释把代码的执行原理讲解清楚。
(4)一个函数执行多个功能
比如 LoadFromFileAndCalculate()函数,它既执行了文件中去加载数据,还执行了计算功能。像这样的函数,我们建议把它切分成两个单独的函数。
(5)不好的代码样式排版
代码的样式排版在某种程度上体现了代码的一种逻辑。好的代码排版能增强代码的可读性和逻辑性。我们在写代码时,要规避不好的代码样式排版。
(6)难以测试的代码
代码没法测试,难写测试用例,这些都是一些不好的表现。
好的代码从哪里来
代码不只是“写”出来的。实际上,在整个项目中,真正的编码时间约占项目整体时间的 10%。好的代码是多个环节工作共同作用的结果。
这些环节包括:
①在编码前,要进行需求分析和系统设计。
②在编码过程中,要注意做单元测试。
③在编码后,要做集成测试,要上线,要持续运营,要迭代改进。
一个好的系统或产品,是以上几个环节持续循环的结果。
接下来我们着重介绍一下重点环节——需求分析和系统设计。
(1)认识需求分析和系统设计的重要性
需求分析和系统设计在软件开发中经常被忽略或轻视,但是这两点都是非常重要的环节。
人们的直觉往往是拿到一个项目就想尽快把它写出来并运行,感觉这样的路径是最快的。
但是实际上在软件前期需求分析和系统设计投入更多的成本,会在后期节省更多的消耗。即:前期更多的投入,收益往往最大。
原因是:如果我们开始的设计做错的话,那么后期开发、测试、上线、调试这些成本都会被浪费掉。
(2)清楚需求分析和系统设计的差别
需求分析和系统设计是有泾渭分明的区别的,为了避免这两者相互混杂,我们需要清楚需求分析和系统设计各自的内涵。
需求分析主要是定义系统或软件的黑盒行为,即:外部行为。比如,系统从外部来看能够执行什么功能。
系统设计主要是设计系统或软件的白盒机制。即:内部行为。比如,系统从内部来看,是怎么做出来的,为什么这么做。
(3)需求分析的注意要点
要点一:清楚怎么用寥寥数语勾勒出一个系统的功能。
每个系统都有自己的定位,我们可以从简洁的总体描述,展开到具体的需求描述。
需求描述的内容基本包括:
①系统类型描述
②系统规模描述
③系统定位和系统差异描述
④系统对外接口功能描述
要点二:需求分析需要用精确的数字来描述。
需求分析中会涉及大量的数据分析,这些分析都需要精确的数字来进行支撑。
(4)系统设计的注意要点
![](https://static001.geekbang.org/infoq/8f/8fab4b1fd683dd695053914786f9e22f.webp?x-oss-process=image/resize,p_80/format,jpg)
要点一、清楚什么是系统架构
系统架构,英文名 System Architectrue。在 wiki 上有一个英文定义阐述了系统架构是一个概念的模型,它定义了系统的结构、行为、更多的视图。
进一步解读系统架构,它的几个要素是:
①系统要完成哪些功能
②系统如何组成
③功能在这些组成部分之间如何划分
要点二、注意系统设计的约束
重点是资源的限制。比如,计算的资源限制,存储的资源限制,IO 网络的资源限制等。
要点三、清楚需求是系统设计决策的来源
精确定义需求中的各个细节,以及量的定义,对系统设计的决策起着重要的作用。
要点四、系统设计的风格与哲学
在同样的需求下,可能出现不同的设计方式。即目的相同,设计不同。比如:复杂指令集和精简指令集的设计差异。
一个好的系统是在合适假设下的精确平衡。一个通用的系统在某些方面是不如专用系统的。每个系统每个组件的功能都应该足够的专一和单一。每个组件是指子系统或模块等。功能的单一是复用和扩展的基础。倘若不单一,未来就有可能很难进行复用和扩展。
子系统或模块之间的关系应该是简单而清晰的。软件中最复杂的是耦合,如果各系统之间的接口定义非常复杂,那么未来便很难控制系统的健康发展。
值得注意的是,使用全局变量就是在增加系统的耦合,从而增加系统的复杂性,所以在系统中需要减少使用全局变量。
要点五、清楚接口的重要性
接口,英文名 Interface。系统对外的接口比系统实现本身还要更加重要,接口的设计开发不容忽视。
接口主要包括:
①模块对外的函数接口。
②平台对外的 API。这些 API 很多是基于 RPC 或者是基于 Web API 来实现的。
③系统间通信的协议。
④系统间存在依赖的数据。比如给另一个系统提供的词表。
这些都是接口,都需要我们去重视,去很好地定义。
接口重要的原因在于:
①接口定义了功能。如果定义的功能不正确,那么系统的可用性与价值便会大打折扣。
②接口决定了系统和系统外部之间的关系。相对于内部而言,外部关系确定后非常难以修改。
接口的修改需要非常慎重且要考虑周全。
后期接口修改时主要注意两点:
第一,合理好用。新改的接口应该是非常合理好用的。不能使调度方感觉我们做的接口非常难以使用。
第二,修改时需要向前兼容。新改的接口应该尽量实现前项的兼容。不能出现当新接口上线时其他程序无法使用的情况。
如何写好代码
关于具体怎么写代码,我们主要从两个维度进行介绍。
(1)代码也是一种表达方式
在一个项目中,软件的维护成本远远高于开发成本,而且超过 50%的项目时间都是用于沟通。
常规意义的沟通方式主要有面对面交流、Email、文档或网络电话会议等。但是其实代码也是一种沟通方式。
在计算机早期,我们使用机器语言或汇编语言,更多的是考虑代码如何更高效率地执行。
然而,随着技术的进步,代码编译器的逐渐完善,我们写代码时更多的是要考虑如何让其他人看得懂、看得清楚。于是,编程规范应运而生。
编程规范主要包含:
① 如何规范的表达代码。
② 语言使用的相关注意事项。
基于编程规范,看代码的理想场景是:
①看别人的代码,感觉和看自己的代码一样。
②看代码时能够专注于逻辑,而不是格式方面。
③看代码时不用想太多。
(2)代码书写过程中的细节问题
关于代码书写过程中的细节问题,我们将从九个角度进行介绍。
①关于模块
模块,是程序的基本组成单位。在一个模块内,会涉及它的数据、函数或类。对于 Python、Go、C 语言这样的程序来说,一个后缀名为.py 、 .c 或.go 的文件就是一个模块。
每一个模块需要有明确的功能。需要符合紧内聚,松耦合。模块切分的是否合理对于软件架构的稳定起着至关重要的左右。
切分模块的方法:
先区分数据类的模块和过程类的模块。
数据类的模块:主要是要完成对数据的封装。封装往往是通过模块内部变量或类的内部变量来实现的。
过程类的模块:本身不含数据。过程类模块可以从文件中去读取一个数据,或者执行一些相关的操作。过程类模块可以调用其他数据类模块或过程类模块。
编写程序时,我们需要注意减少模块间的耦合。减少模块间的耦合,有利于降低软件复杂性,明确接口关系。
②关于类和函数
类和函数是两种不同的类型,有他们各自适用的范围。另外,遇见和类的成员变量无关的函数时,可以将该函数抽出来,作为一个独立的函数使用,这样便于未来的复用。
③关于面向对象
面向对象,是一个优秀的编程方法和范式,但是真正理解的人并不多。
面向对象的本质是数据封装。这就要求我们在写程序的过程中应该从数据的角度开始想问题,而不是从执行过程的角度开始想问题。
我们需要注意一个普遍的错误认知,即:C 语言是面向过程的,C++是面向对象的。
实际上,C 语言是基于对象的,它和 C++的区别主要是没有多态和继承。
C++是一个经常被滥用的语言。因为 C++有太强的功能。
作为软件工程师,我们最重要的任务是去实现出我们所需要的功能,语言只是我们的工具。
另外,在系统中,我们应该谨慎地使用多态和继承。如果一个系统中,类的继承超过三层,那么这个系统的复杂度便很难把握。
有这样一个悖论:很好的继承模型是基于对需求的准确把握,而在我们在初始设计阶段往往对需求理解的不透彻。系统在初始阶段可能只是一个很简单的原型,然后通过不断地迭代完善,才逐步发展起来变好的。
④关于模块内部的组成
一个模块,比如.py、.c 或.go 这样一个模块,它的内部组成主要是:在文件头中,需要对模块的功能进行简要说明。需要把文件的修改历史写清楚,包括修改时间、修改人和修改内容。在模块内,内容的顺序尽量保持一致,以方便未来对内容的搜索查询。
⑤关于函数
函数的切分同样是非常重要的。对于一个函数来说,要有明确的单一功能。
函数描述三要素包括功能、传入参数和返回值。
→ 功能描述是指描述这个函数是做什么的、实现了哪些功能。
→ 传入参数描述是指描述这个函数中传入参数的含义和限制条件。
→ 返回值描述是指描述这个函数中返回值都有哪些可能性。
函数的规模要足够的短小,这是写好程序的秘诀之一。bug 往往出现在那些非常长的函数里。
在函数头中,需要对函数的语义做出清晰和准确的说明。我们需要注意函数的返回值。在写函数时,要判断函数的语义,确定返回值的类型。
基于函数的语义,函数的返回值有三种类型。
第一种类型:在“逻辑判断型”函数中,返回布尔类型的值——True 或 False,表示“真”或“假”。
第二种类型:在“操作型”函数中,作为一个动作,返回成功或失败的结果——SUCCESS 或 ERROR。
第三种类型:在“获取数据型”函数中,返回一个“数据”,或者返回“无数据/获取数据失败”。
在这里,推荐一种函数的书写方式:以“单入口、单出口”的方式书写。这种方式能够比较清晰地反映出函数的逻辑。尤其是在实现多线程的数据表中,推荐使用一个内部函数来实现“单入口单出口”的方式。
⑥关于代码注释
要重视注释,书写注释要做到清晰明确。在编写程序的过程中,先写注释,后写代码。
⑦关于代码块
代码块的讨论范围是在一个函数内的代码实现。书写代码块的思路是先把代码中的段落分清楚。文章有段落,代码同样有段落。代码的段落背后表达的是我们对于代码的逻辑理解。包括代码的层次、段落划分、逻辑。代码中的空行或空格是帮助我们表达代码逻辑的,并非可有可无。好的代码可以使人在观看时做过一眼明了。
⑧关于命名
命名包括系统命名、子系统命名、模块命名、函数命名、变量命名、常量命名等。
我们要清楚命名的重要性。命名重要的主要原因为:
一是“望名生义”是人的自然反应。不准确的命名会使人产生误导。
二是概念是建立模型的出发点。好的命名是系统设计的基础。
命名中普遍存在的问题有:
一是名字中不携带任何信息。
二是名字携带的信息是错误的。
命名不是一件容易的事情,关系着代码的可读性,需要仔细思考。命名的基本要求是准确、易懂。提高代码命名可读性的方式之一是:在名字的格式中加入下划线、驼峰等。
⑨关于系统的运营
在互联网时代,系统非常依赖运营。并不是我们把代码写完调试通了就可以。
在系统运营过程中,代码的可监测性非常重要。很多程序都是通过线上的不断运行、不断监测、不断优化而迭代完善的,所以我们在编写代码的过程中,要注意尽可能多地暴露出可监控接口。
对于一个系统来说,数据和功能同等重要。
数据收集很重要,数据量够大才能知道这个项目或这个系统的具体收益。
关于系统的运营,我们在设计和编码阶段就需要考虑。即:在设计和编码阶段,提供足够的状态记录,提供方便的对外接口。
怎样修炼成为优秀的软件工程师
通常人们在判断一名软件工程师的水平时,都会用工作时间、代码量、学历、曾就职的公司等等这类外部因素作为评判标准。
但是事实上修炼成为优秀的软件工程师,重要的因素有三点:
1、学习-思考-实践
2、知识-方法-精神
3、基础知识是根本
学习-思考-实践
(1)多学习
软件编写的历史已经超过半个世纪,有太多的经验可以借鉴学习。要不断的学习进步。
(2)多思考
学而不思则罔,思而不学则殆。对于做过的项目要去深入思考,复盘写心得。
(3)多实践
要做到知行合一,我们大部分的心得和成长其实是来自于实践中的经历。在学习和思考的基础之上,要多做项目,把学到的理论运用到真正的工作中。
知识-方法-精神
互联网的发展日新月异,对于软件开发来说,知识永远在增加,所以在变化快速的知识世界中,最好的方式是找到方法。
方法就是用来分析问题和解决问题的。虽然说起来简单,但是适合每个人的方法都需要自己去寻找和总结。
在大多数人的成长过程中,并不单单只是鲜花和掌声,更多的时候是在和困难荆棘做斗争。而真正能做出成就的人,都有着远大理想和宏伟志向。所以,光有知识和方法往往是不够的,还需要有精神作为支撑。
在这里推荐给大家几个精神理念:
(1)自由精神、独立思想。
人一定要有自己的思考。不要人云亦云,不要随波逐流。
(2)对完美的不懈追求。
不要做到一定程度就满意了,而是要去不断的追求一个更好的结果。
基础知识是根本
唐朝著名宰相魏征曾经对唐太宗说过:“求木之长者,必固其根本;欲流之远者,必浚其泉源”,充分表达了基础乃治学之根本。
对于一个软件工程师来说,需要掌握的基础是非常全面的。
![](https://static001.geekbang.org/infoq/a3/a36e179b5666a0ace9e688bb8601f442.webp?x-oss-process=image/resize,p_80/format,jpg)
包括数据结构、算法、操作系统、系统结构、计算机网络。包括软件工程、编程思想。包括逻辑思维能力、归纳总结能力、表达能力。还包括研究能力、分析问题、解决问题的能力等。这些基础的建立,至少也要 5~8 年的时间。
欲速则不达,希望大家能够踏下心来,尤其是在自己职业生涯开始的几年,将基础打好。这样,才能在未来有更长远的发展。
版权声明: 本文为 InfoQ 作者【百度开发者中心】的原创文章。
原文链接:【http://xie.infoq.cn/article/cf2af7de3455327fe41d77d68】。文章转载请联系作者。
评论