如何写好程序(下)

开发出好程序要做的功课

接著前一篇,原则设定好了,接下来还需要能够实践,否则就只是空谈,而实践就得依赖开发人员的素质。要提升开发人员的素质,有三个重点, “基础、基础、基础” 。没错!想要提升素质其实没有速成的方法,就像功夫电影都会有强调扎马的桥段,脚力是提供一切招式力道的根源,而再凌厉的招式也可能因为重心不稳而失去了威力。开发的领域中,愈充实的基础知识在遇到状况时愈能有效的解决问题,甚至是提早预防。因为在知道有哪些现成选项可以运用,也就不需要重新打造一个轮子,而是站在巨人的肩膀上。

心酸的业界生态

身为开发者必须要很清楚自己设计的每一个环节会产生的效果或可能的问题、限制,才能够贴切地符合需求的内容。这是需要具备一定的经验和知识,尤其是随着时间的累进、程序愈来愈精致化,开发程序时不论是编程语言或是作业平台都伴随着庞大的内容、大量的资讯,需要时间来消化、理解。

入行的条件不再高不可攀

也许是现代开发工具的强大与坊间的教育机构的普及,很多人经过简单的训练就以 “会把网络上搜寻来的程序片断兜起来、可以正常运行” 当成是写程序的全部。但是科班出身的就充满着光芒吗?我也看过不少人,不是在课堂上虚掷光阴,就是没有将所学融会贯通,展现出来的工作水准也只是一般般。

现在写程序的模式除了有现成的框架、库可以选用,网络资讯的发达让形形色色的源代码片段都可以搜寻得到,很多时候只要选好框架、把网络上的源代码片段组合起来,就可以有一个能够运行的程序。当然没事就没事,但不幸出事了要算是谁的责任?

在过去的工作经验里就曾经遇过当程序发生问题,我问负责的人出问题的源代码在做什么事,他回答我: “不知道,从网络上贴来的!” 如果你是老板,遇到这种情况会不会想:“那我应该付钱给原作者?还是负责贴上的人?”

当你不了解程序运行的内容,怎么能保证你的程序能够稳定地运行、没有安全上的疑虑?的确,在实作上有很多时候需要进阶一点的演算法,像是压缩、图形运算,非科班出身、没有数学基础的还真没有办法理解。有点职业道德的人会尽可能地做好测试,就算不知道内容,但最少确认运行的结果是自己要的。而不是交差后就当没事,默默祈求程序不要出问题!

这个情况,让人联想到食安事件。有一部份餐饮业者没有具备烹饪的基本功夫,所以很多材料必须要靠外购,对食材的特性也没有足够的掌握能力。但在成本的考量下,黑心一点就选择明知有问题、来路不明但成本低廉的食材。像是用的油出了事、用到了地沟油,就只能推说: “我也是受害者,是那些做地沟油的厂商该死,应该要向地沟油的厂商求偿,与我无关。”

当然,也有负责任的店家并不是用成本低廉的食材但也遭到波及。第一时间会觉得这些人真的很无辜,但进一步想想,要从事餐饮业是你的选择,你的客人是信任你的烹饪技巧、口味的掌控、食材挑选能力等等的质量要素,而不是信任你的进货厂商。出了问题,要说你完全没有责任并不合理!对消费者来说,合理方式应该是先赔偿给消费者,至于和进货厂商的赔偿问题是店家内部的事,这样对我们这些末端的消费者才公平不是吗?而且,规模大的商家更应该要有资源、能力与责任承担起这样的标准。

写程序不再是闭门造车

反观我们自己理所当然地把别人写好的东西拿来用,却鲜少深究内部运作的逻辑,是不是也是一样的缺乏商业道德?说起来还挺心虚的,批评地义正严词、掷地有声,原来比起食品产业我们高尚不到哪去!用 “我们” 似乎太过一竿子打翻船人,所以我招认最少我自己是。

在社会分工细致化、讲求效率的年代,工作当中使用自己不了解的产品成了必要之恶,就像是要求店家所有的食材全部自制一样,是一种奢求。再加上讯息科技虽然发展没几年,但是已经形成了一个很庞大的知识体系。不一而足的操作系统平台、程序语言,多如繁星的框架、SDK、库,各行各业的领域知识、商业逻辑,每一个区块都形成了一个有高度的门槛和学习障碍。在开发程序时,如果要完全以自制的模式来进行,不是成本高得吓人,就是时间长得不能接受,以致于难以实行。

知名厂商的库、开源的框架比较不可能会是问题的来源,毕竟这些程序都是以一般化的用途为出发点、能够适用于大多数的情况之下,并且经过重重的检验、测试。知名的公司也不可能自毁招牌,放任产品中有明显的瑕疪或安全问题。但由网络上搜寻到的源代码往往是示范性质或专为解决特定问题,不见得与程序的需求百分之百的契合,如果一字不改或是对运行的内容一知半解的情况下编入程序内,这样的做法和之前举的食安例子中使用来路不明食材的店家有何差别?

那有些小公司或不明来源编译好的库岂不是问题潜在的制造者?会不会内藏恶意的源代码?这个问题对于管理者来说会是一个取决于风险与成本的困难抉择。端看你的程序对于安全的要求程度,是企业内部敏感数据的系统?还是给一般人使用的系统?打算付出多少的成本?没有打算付出太多的成本,只能承受较高的风险;不能承受风险的,就得要付出较多的成本,像是有制度一点的应该要成立专职的安全小组对程序做必要的检测。

如果要问我,所开发的软件遇到像食安风暴的情况,会不会采用和那些店家一样的态度?我会,同时还会用投名状中庞青云的语气说 “装死无敌!这是规矩!” 。但这是问题发生了的假设,在发生问题之前我会想要尽可能地避免,毕竟程序是自己写的,光嘴巴上喊没责任就可以置身事外也不太可能。

追求质量的价值在哪里?

要把这个想法做好,首先会遇到的就是被海量信息淹没的无助。这是这行从业人员普遍会有的心酸,要学习的东西愈出愈快、愈来愈多。先不看是不是有足够的时间吸收,光信息量就已经超出一般人大脑能够承载的极限。餐饮界里随处可见几十年的老味道,能够传承好几代的手艺。反观软件界,当年纵横 COBOL 和 Fortran 语言的系统,试问如今安在?姑且不看这种由时代浪潮所带来的兴衰,同一家公司的技术总会一脉相传、延续演进吧?过去靠微软的技术混过饭吃的那段日子,有过不少次被微软打脸的经验,那头才在研讨会上大张旗鼓地热烈推销,回头却是悄悄收摊,不再发展、更新。收兵尚且要鸣金,有的时候是连鸣金都省了!我的青春、我的人生,就在不断地用新技术翻修旧系统的循环中渡过。

信息技术的更替如过江之鲫的现象,再加上公司高层多以利字为先,造就了不少开发人员速成的心态。对使用的技术一知半解、得过且过,源代码能用就好、不求甚解,反正用没多久又要换一种写法,复制贴上的时间已经比自己打字的时间还多。而衍生出来的问题是遇到了技术上的状况,常常磨了很久得到的答案是不可能解决、没办法处理。但等到换一个对技术比较自我要求的人去试时却又不是这么回事,有时解决的方案还直接到不行,可是原本负责的人找答案时又认真到你分不清楚是否是该归类成偷懒的借口!这样的情况我遇到的并不是个案,也不是单一个人才有,甚至习以为常到不忍去苛责这个大环境所形成的共业。就管理者的角度,迫于专业分工下的现实,只能尽可能让开发人员针对自己擅长的领域做专精,用团队的力量来截长补短。

既然业界风气如此,那一昧的追求开发人员的素质是否太过矫情?还是个人完美主义在作崇?组合网络上的源代码所产出的程序真得有这么不堪、有这么严重吗?就如同做吃的这件事,没有高超的烹饪技巧、专业级的食材挑选眼光,就不能煮东西给别人吃了吗?那这样岂不是所有家庭的爸爸、妈妈都不能下厨,都只能外食?如果我有一道拿手的菜就不能分享给亲戚好友、街坊邻居?

我个人认为,程序本来就是解决问题的工具,如果写的人快乐、用的人也快乐,是否得这么吹毛求疪地一定要设定什么门槛或标准?我想并不是这样的,业界常常有一句话:“没有最佳的解决方案,只有最适合的解决方案”。所以在这里完全没有要贬低不是接受传统制式训练的从业人员,或是对于愿意投入热情从事程序开发工作的人有任何不敬的意思。相反地,能够用程序解决生活中或是专业领域上遇到的问题,是很值得敬佩的一件事。最少人家有第二专长,而我只能靠人家的第二专长糊口,光这一点就没什么资格对人说三道四的。

不过,这文章怎么好像愈写愈没立场,那还谈什么素质的提升?先来看看 Uber 和 Airbnb 的所带来的争议,这二个平台所使用的模式很类似,都是共享经济的实践者,分享私人闲置的资产来对大众提供服务。像是如果我有闲置的车子我可以开着它去载客;如果我有闲置的房间我可以提供给短期需求的人来住宿。听起来很美好,不但是各取所需,还可以有效利用资源、落实环保的概念。

但为什么有人要出来反对、抗议,而政府也介入干预?是政府狗拿耗子?那些既得利益者眼红?如果是眼红,那出租车司机为什么不叫车队也搞一个类似的平台来竞争就好?我用自己的车载朋友也要证照?加入 Uber 载人违法,自己载朋友不违法,差别在哪?是因为没有从中获利?朋友请吃饭当谢礼算不算从中得利?我使用的是全新车,车款高级、内装豪华,载客时也是西装革履,开车时战战竞竞,车上还有额外的饮水服务,哪一点比出租车差?明明双方都是你情我愿,又没碍着别人,这样也要抗议?那有厨师证的为什么不出来抗议满街没证照的街边小吃?开店做生意的店家为什么不出来抗议摆在路旁的地摊?

上面是延伸前一段的立场只呈现单一面的意见,而实际上两造的论点各据一词、没有太大的交集。但可以看到一个现象,都是提供服务的二方在争议,购买服务的第三方却没听到很大的负面评价。我想那是因为还没有遇到危害切身利益的情况,以服务提供者的角度来看,重点还是那句话:没事就没事,但不幸出事了要算是谁的责任?一但牵扯到利益,问题就会变得异常的复杂,不管你是争议的哪一方、有没有政府背书的证书、或是你从哪个名校毕业的。面对的若是亲朋好友,可能会看你的情面,不用你开口,就主动放弃权利,也许帮忙出个必要的费用就当没事。但如果是你不认识的人,你有把握可以说服苦主让你轻松过关吗?烂客人事件在新闻中屡见不鲜,当你碰上了一个要跟你走法律程序的对象,你真的有时间、心神应付?

所以,如果你对于写出好程序还有一点期待,不管你是完美心态作崇、还是想要明哲保身,提升质量是一项看得见回报的投资,所谓一分耕耘一分收获,端看你想要先甘后苦还是先苦后甘。也就是说,看你想要先花时间避免未来的损害?还是你想要把时间用在出问题后擦屁股这件事情上?

基础、基础、基础

能做的努力大概就是一开始提到的:基础,多一分了解才多一分的机会知道怎么逃避责任... 我是说保护自己,就像法律通常都是保护懂法律的人。当然在软件的工作领域中,很多是商业机密、只能用不能看,在没有授权的情况下太 “深入” 是会有触犯法律的风险。所以这里指的努力,当然就是针对公开的部份,像是文件、开源码,至少让自己不是在一知半解的情况下做出错误的抉择。

具备关键的知识

基础中的基础不外就是在学校中教授的那些课程,像是计算机概论、操作系统、数据结构、工程数学等等。不过,这些内容却也是在实际从事程序开发时最不具存在感的内容。现在的程序设计都架构在层层的堆叠与包装之上,很多底层的问题都被隐藏、处理掉了。像是很多的语言都有提供垃圾回收(GC)的机制,所以 Memory Leak 问题变得不需要花时间处理,原本课堂上学的 Memory Leak 注意事项也无用武之地。

但这些设计上的问题仍然实际存在着,有一些仍然是要靠开发人员素养来做出适当的抉择。在选择变量类型来储存数字数据时,有些人会想容量应该是愈大愈好,以免数字不小心超过上限而让程序出现问题。在没有接触过相关的知识会根本没想到,变量的型别会对运作的效能上产生影响。而造成效能影响的原因,来自变量型别与记忆体长度的关系,是底层一个意想不到的运作规则

还有像是之前提到的多线程在设计时会遇到诸多的问题,如果具有相关的知识就会了解系统底层的工作原理、多线程的管理方式、对应处理的机制。数据结构可以协助设计有效率的数据处理模式,像是如果要进行排序会有许多的排序演算法可用运用,每一种都有其特性和适用的时机,并且有一套学术上的方法可以预估处理模式在效率上是否符合需求。同时也会知道递归很好用,但呼叫阶层数太多程序是会当掉的。如果要计算 1 加到 N 的总合,第一直觉是使用递归来写,写出来的程序却有个致命的缺点,N 有上限、超过程序会当掉。可是改用梯形面积公式,程序只要一行、运行时间天差地别,N 也可以理论上达到无限大,最少可以达到变量能储存总合的最大值。

这些相关的机制大多会被实作在编程语言所搭配的 SDK 上,技术文件中也许只会提到使用的规格,而不会有详细的说明来解释背后的原理。当你拥有相关的背景知识就可以适切的运用这些功能,而不是靠着猜测与试验来决定要选择哪一种方案。这些选择对程序的稳定性与运行效率都会有举足轻重的影响,也决定着程序一但出现问题时初步处理的策略,发现及解决问题所要耗去的时间。

数学更是这一些基础知识最不起眼的,就像我过去在企业 IT 部门服务的经验里,高阶的数学知识完全束之高閤,大多数的程序只是单纯地要求数据库的 CRUD,数据处理的运算需求也顶多是加减乘除。不过,数学基本上可以训练逻辑推演、协助思考,如果能够融会贯通是有机会可以让自己增加薪资条件、进入特定的专精领域,像是图学里的演算多是靠矩阵、向量等运算来进行,这是在影像处理、游戏、3D 应用中必须的。

同时,没有明显的助益并不代表就是多余的。这些知识会成为基础是有其道理,所有的人造物都不会是跳跃式地一下就以复杂的结构出现,都会是由简单的规则组合并堆砌所演化出来的。这个理论套用在软件产业中一样适用,基于相同底层所发展出来的系统都会具有一定的相似度。所以吸收的这些知识会内化成为你的工作能力,例如增加技术的敏锐度,提升学习的效率,更容易接纳新的技术、缩短上手时间。这就好像如果你有富爸爸,做起事来一定是事半功倍;但反过来如果是白手起家,遇到困难就是只能一步一脚印地走出来了。

掌握编程语言

再接下来的这项基础效果就会比较明显一点,那就是编程语言编程语言是程序开发的核心,用来编译成 CPU 可运行机械语言的依据。而编程语言靠的是保留字来跟编译器沟通,透过保留字设定好的意义来转换成机械语言字节码

编程语言是所有人接触程序的切入点,学习编程语言的脉络不外是变量宣告运算符陈述流程控制,进阶的还有面向对象程序设计,但有不少人对于编程语言的学习也就仅此而已。随着时间的推演,不少的新概念被导入既有的编程语言之中,像是现在最新的是大家都流行要 Lambda 一下,再早一点是使用 Attribute 来提供源代码在运行时期的附属资讯,或是利用保留字来简化多线程环境的数据同步问题。

这些编程语言的新语法如果不会对写程序造成改变,通常也都不会、毕竟还是要考虑到相容性,很多人的选择都是视而不见。就算是以喜新厌旧闻名的信息产业,仍存在着为数不少的坚持不轻易改变的从业人员。姑且不看新的语法所带来的撰写上的弹性、工作效率的增加、运行效能的提升等优势,就算是只安于剪下贴上的工作模式,当网络上搜到的示范源代码愈来愈多是以新语法撰写时,打算如何衔接到所负责的源代码内?或是有一个对新技术高度狂热的同事写的源代码要让你接手,这时还能够以不变应万变吗?

只有新玩意才不被人重视?泛型就不是个新玩意,但没听过、不知道如何使用还大有人在,不然就是只会依样画葫芦地呼叫以泛型写的功能,自己要设计却无从下手、也不了解别人含有泛型设计的源代码。所以对编程语言不够了解会导致的下埸就是看不懂别人写的源代码,能被分配的工作就只能是只写不改,可能吗?

虽说看得懂别人写的源代码,是发现逻辑错误或语法问题的第一步,看懂了却忽略细节一样也可能对错误视而不见。有一个很古老的案例,以下有一个在 VB 还没有进入 .NET 的时代的源代码范例:

Dim A, B, C As Integer

我遇到很多人都以为三个变量全部是以整数类型储存在记忆体中,但实际上却是只有 C 是整数类型,另外二个是以 Variant 的型别来运作。这个问题严重吗?就 VB 的语言特性来说,平常不会出现什么异样。只是当问题出在 Variant 上,源代码的差异一下没看出来,要确认问题发生的原因还是挺累人的。

还有一个很基本却是不少人忽略的细节,那就是变量的生存周期、作用域的议题。看似不经意的变量声明动作,影响所及是程序会不会虚耗宝贵的记忆体空间、占着毛坑不拉屎;也有可能变量的内容在没有料想到的惰况下被更改,让程序时不时地出现莫名其妙的反应,却又遍寻不着发生问题的原因。

善用开发的工具

要开发程序,绝大多数的人大概已经脱离不了对 IDE 的依赖,一个整合好开发所需环境的工具。从单纯的打字、编译、调试等基本的开发功能,到源代码提示、即时语法检查、源代码重构、版本控管、源代码复杂度检查、效能检测、测试报告等跟团队与质量相关的功能。能做的事愈来愈多,但我们在进行开发工作时对 IDE 提供的功能中能够掌握运用的比例有多少?

很多跟 Application Lifecycle Management 相关的功能也许不是负责开发工作的人说要用就用得上,但还有不少是和工作效率习习相关的可以快速发现问题、缩减开发工作负担的功能。曾经就有遇过有人在调试时,仍在使用最基本的左右逼近法,完全不知道有条件断点可以用。很难想像在遇到大型循环时,怎么有办法耐得住性子,一次又一次地按着单步运行的按钮,直到期望的条件出现;又或者是在很多阶层的递归之间,是如何保持清醒、很明确地知道现在程序走到了第几层的呼叫?

“工欲善其事,必先利其器” 大家都知道,很多人也羡慕 MacGyver 可以用一把瑞士军刀就可以解决很多问题,但却在自己拿到瑞士军刀时,只知道使用其中一项工具来解决所有问题。有的时候,功能的使用是需要有一点创意才能找到符合自己习惯的模式,但最少应该要先点开来把玩看看,才能借由熟悉找到最合适的使用情境。

熟悉程序运作的环境

这里的环境指的是由操作系统内建函式、官方 SDK、各式框架、第三方库所组成,用来提供程序运行时期一切运作的基础。既然是基础,就是在提升开发人员素质里所要讨论的范围内。

这个部份还真的是知易行难的典范,跟学校教授的课程相比变迁剧烈、跟编程语言的内容相比更加庞杂无边、跟开发工具的功能组合相比更是繁复多变。随便挑一小区块的资讯都可能看到成了过去式还看不完,是很心酸,但无奈之余最少应该要对工作所需要的领域有 “基本的认识” 。

过去有接触过不少有所谓的 “开发 Android 程序” 经验的人,对谈之后发现这些人对 Intent, Service, Content Provider, Broadcast Receiver 等等基础的控件完全没有概念,不要说没有使用的经验,甚至连这些名词都一无所悉。还有的根本不知道自己写程序用来控制画面的控件叫 Activity,当然也不用提进阶一点的 PendingIntentIntentService。这应该已经不是专不专业,而是合不合格的问题了!

再来看一个架构大一点的例子,在因应 RESTFul 轻量化需求而诞生的 ASP.NET Web API 之前,WCF 一直占据着微软远程过程调用的主要舞台。WCF 借着简化的开发过程、弹性的设定档调整,让不具底层知识的开发人员也可以轻易的建立跨电脑传递的功能,成为了企业内架构分散式系统的好选择。但随着移动平台的堀起,移动装置要被纳入分散式系统成了热门的需求。这时,不知 HTTP 规格是何物的开发人员就踢到了铁板!要让没有 WCF 的移动平台可以呼叫的情况下,限制服务端只能用 HTTP 通讯是必然的。然而安全性WCF 主打的特色之一,服务端一定设计了相当程度的安全验证,没有 WCF 怎么通过验证?

该想到的情况 WCF 都想到了,没想到的也保留了接口可以扩充,调整设定参数就可以完成准备。移动装置要如何送出验证信息却是尚待解决的问题,如果送不了,是不是把服务端的安全机制撤下来?有资安这种单位在,这个想法是不可能的!为了移动平台再重新打造一组服务?如果有闪电侠在可以考虑一下!这时如果对 HTTP 的规格有一定的了解、研究过 WCF 设定细节,就可以知道 WCF 要进行何种设定,并且在移动平台送出要求时要附上什么样的验证资讯,以最低的成本来达成任务。

对于所谓 “基本认识” 的作法,每个人天赋不同、做法也因人而异,也许有神人能够把所有的资讯记忆在脑海之中。我的习惯是最少先浏览一遍文件的目录或大纲让脑海中有一个印象,然后期望体内的小宇宙能在遇到障碍时,提醒我排除的方法可能存在于文件的某一处。有的时候的确会很神奇的灵光一闪、小有帮助,多数的时候还是得靠 Google 大神,有道是团结才是力量嘛!只是就算 Google 再神,也得要知道怎么问、下什么关键字,这就要看平常阅读的功夫下得有多深了,也就是一开头提到的 “基础、基础、基础”。

最后

总算,文章的篇幅很长,大多是过去工作遇到状况的缩影。叨叨絮絮地打了很多,是希望能让刚接触这个行业的人多些参考。感谢能够这么耐心地看到最后的各位!

推荐阅读更多精彩内容