技术开发 频道

打造稳固的程序码,先从思路方向着手

【IT168 分析评论】

    程序员专注的焦点,常放在正常的使用状态,但使用者可能胡乱输入,所以要考虑异常的使用情境,以打造更稳固的程序码。

    对程序员来说,撰写程序的目标就是要完成功能,不论任务是要撰写一整个系统、一个程序库、一个模组,还是一个函式,或者所撰写的程序码单位是什么,在实际开发时,总会有个功能性的目标,希望所完成的程序码,能够提供特定的功能。

    初学者最重要的进阶议题:打造更稳固的程序码

    对初学程序设计的程序员来说,撰写程序码时,不可避免地,会将所有的焦点及心力放在达成功能,毕竟这是最起码的要求,倘若连功能都不能完全实作出来,那么更甭提其他事情了。

    随着经验渐渐增多、技巧逐步纯熟,在撰写程序码时,他们所关心的事情,慢慢地,就会不仅仅限于如何实作出功能。例如能不能让程序码执行得更有效率,以及如何让程序码更具可读性、更容易维护,或许还会关心如何让程序码容易修改、容易扩充。这些事情都是在满足基本的温饱(达成功能)以外的额外考量。

    这几个例子,都是许多初学者会涉猎到的进阶议题。此外,或许有一个主题,是比较少探讨到的,但我相信它的重要性,不在上述进阶议题之下。这个主题就是如何打造更稳固的程序码。

    程序开发除了情天情节,也要想到雨天情节

    所谓程序码的稳固性(Robustness),意指程序码进到一个非常态的执行状态时,能否适宜地因应。此处所指的非常态的执行状态,有可能是资料库连线无法建立、无法开档或写档、所接收到的一个变数指标为Null……等。

    而所谓适宜地因应,或许是平和地终止系统,并留下清楚的讯息,以指出所遭遇的异常状态。或许是停止执行并回传相对应的错误代码,也有可能是尝试修复系统所遭遇的异常情境,使它再度回到正常的状态。

    当程序员投注所有的心力,试着要达成程序的功能时,焦点几乎都会放在程序的正常状态。一般来说,我们会将所预期的正常执行路径称为晴天情节(Sunny Day Scenario),而异常的执行路径则称为雨天情节(Rainy Day Scenario)。当我们只关心如何达成程序的功能时,几乎就只考虑到晴天情节,也就是假设所有的条件、参数以及状态,都落在我们预期的理想范围中。

    你有没有过这样子的经验:有些程序员总是能交出看起来似乎能够运作的程序码,倘若针对他的程序码做一些简单的测试,他的程序码看起来就像是依照规格般地将功能实作完毕。但是,倘若将这段程序码放到雨天情节下执行,会发现它不仅不能正常地反应,有时甚至会让整个系统无预警终止(最常见的,莫过于存取空指标,而造成的记忆体违规存取动作)。

    雨天情节:假设使用者有可能是只猴子

    或许刚入门时,你会觉得光是在晴天情节下达成功能,就是一件不简单的任务。但是,当实作经验增多,会发现在各式各样的雨天情节下让系统生存下去,难度相形之下更高。各式各样的雨天情节,正是突显出程序员功力高下的关键因素。

    优秀的程序员所写下的程序码,在达成需要的功能之后,同时也一并考虑一些可能遭遇到的异常情境,并且适度地加以处理,不致于丝毫放任系统进入不可预期的状态,并且做出不可预期的行为,甚至是无预警地坠毁。

    这中间不仅仅只是技巧面上的差别,更重要的是思路方向的完全扭转。多年前,我曾写过一篇名为「未虑对,先虑错」的文章,是在探讨这种截然不同的思路方向。下棋时,人的思考习惯是「未虑胜,先虑败」,也就是在落子前还没想到胜过对手的情境,却已经把各种可能的失败情况思虑透彻。撰写程序码也是一样,「未虑对,先虑错」代表你的思路中,对雨天情节的考量,在正常情况下的晴天情节之前。

    有许多程序员处理一个物件时,不会考虑到这个物件可能是Null的情况;当他们拿到一个阵列的索引值时,不会事先想到这个索引值可能超出阵列可允许的存取范围;当他们开启一个档案时,不会计划到这个档案或许不存在;当他们试着建立一个网路连线时,不会注意到连线可能会建立失败;他们甚至不会取得所呼叫的大多数函式的回传值,以进一步检查这些函式的叫用动作是否失败。因为他们的焦点完全就只放在最理想的正常执行路径上。不过,天气总不是天天都放晴,有时多云有时阵雨,甚至还会刮台风。

    只考虑晴天情节的程序码,若是埋藏在系统中,像是未爆弹,随时都可能被引爆的。倘若测试案例的涵盖范围不够大,那么便有可能无法查觉潜在的问题。一旦遇上了下雨天,系统就要出事了。这样的程序码大大降低了系统的稳固性。

    当你撰写程序码的态度是「未虑对,先虑错」时,基本上是以怀疑与不信任的眼光来看待所面对的一切。对于每个变数,你会先在心中琢磨,它们的值是不是有可能是非法的值。对于每次的函式叫用动作,你都会假设它们有可能执行失败。你甚至会考虑到,其他人所提供的函式,或许还包含实作上的错误。

    就像当你实作自己的函式时,会假设使用这个函式的客户端程序,有可能根本就是胡乱使用,对方所传进函式的引数完全都是非法的值。当你实作与使用者相接的程序时,你会假设使用者根本就是毫无章理地胡乱输入,或许你还假设使用者有可能是只猴子。

    极限编程即是一种「未虑对,先虑错」的思考习惯

    近年来,有许多人(例如极限编程XP的爱好者)主张采用测试先行的开发方式。也就是说,在撰写真正的程序码前,先写好测试这段程序码的测试案例。事实上,这便是一种「未虑对,先虑错」思考习惯的展现。

    撰写测试案例和撰写程序码时会抱持的心态,两者恰好相反。撰写程序码时容易正向思考,偏向思考如何在正常情况下达成功能。但撰写测试案例时,脑子里想的,往往净是如何提供各种程序可能没有考虑到的情境条件。

    当你采用测试先行的开发方式时,会在撰写真正的程序码之前,仔细地在脑中将可能的各种情况一一展开。那么,当你真正进到撰写程序码的阶段时,自然而然会将更多的可能情况纳入考量,并且加以处理。

    扩展思考的范围,有助于写出稳固的程序

    有许多程序员会利用所谓的Assertion(维护),来检查程序所能处理的执行状态。在某些语言中,它是以函式库的方式实作,或是巨集,而像在Java程序语言中,甚至提供了专门的算式语法。不论究竟是以何种方式实作Assertion,目的都是让函式的撰写者检查函式执行时的状态是否符合预期,例如检查所传入的某一个指标不为Null。

    虽然Assertion仅在开发除错阶段才会起作用,并且被视为是一种开发时除错的有用工具,而在正式的释出版本中,并不含Assertion的机制。

    但是对程序员而言,养成在程序码中运用Assertion的习惯,意谓着在撰写程序码的同时,总是更彻底思考了撰写的程序码,所能够因应的执行状态,并且明白地表明了程序码所不能接受的执行状态。这种思维方式,自然有助于程序员将思考的范围扩展得更为广泛,因而更有可能写出较为稳固的程序码。
 

0
相关文章