最近在监理一个项目,因此也就看了一群程序员写的代码。简单的说感受,现在很多程序员就是一群造粪机器,写的代码就是一坨屎,臭不可闻。
当然了,从软件工程学、项目处理甚至于成本考虑,这群程序员做的程序并没有什么问题。但从长远考虑这是一个劣币驱逐良币的过程,而且现在的良币已经被驱逐得差不多了。
早上和这群家伙开会,会议上说的话有点重——“你们每行代码都是对行业的侮辱!”
为什么生这么大的气?这是一个很小的项目,却引入了30多个pip库,最后却卡在了一个质数判断的问题上。
事情很简单需要判断出0-31这三十二个数字哪个是质数。由于之前W君说过不要为了完成一行代码去引用一个巨大的pip库,就让这群孩子们犯了愁了。
于是就出现了这样的代码:
def is_prime(n):if n <= 1:return Falsefor i in range(2, n - 1):if n % i == 0:# 找到了一个除数,不是质数return False# 循环结束,没有找到除数,是质数return True这行代码对不对呢?理论上是没有错误的,毕竟,当初上学的时候大多数老师会这么教的,这段程序完美的展现了上学的时候老师说的“指在大于1的自然数中,除了1和该数自身外,无法被其他自然数整除”的质数最基本含义。
显然,这比很多只会用python这种胶水语言无脑粘接各种库的程序员强了那么一点点。
不过,这件事依然不对,W君就说了一句“你们再想想”。
于是代码就改成了这个样子:
def is_prime(n):if n <= 1:return Falseif n == 2:return Truefor i in range(2, n - 1):if n % i == 0:# 找到了一个除数,不是质数return False# 循环结束,没有找到除数,是质数return True程序员小A的说法是当n=2的时候,for循环实际上是没有执行的,于是程序员小B则进一步跟进还得加上n==3的判断:
def is_prime(n):if n <= 1:return Falseif n == 2:return Trueif n == 3:return Truefor i in range(2, n - 1):if n % i == 0:# 找到了一个除数,不是质数return False# 循环结束,没有找到除数,是质数return TrueW君当时就气笑了,说了一句“你们离真相更近了一步”,于是几经讨论,他们拿出来了这个!
def is_prime(n):if n <= 1:return Falseif n == 2:return Trueif n == 3:return Trueif n == 4:return Falseif n == 5:return Trueif n == 6:return Falseif n == 7:return Trueif n == 8:return Falseif n == 9:return Falseif n == 10:return Falseif n == 11:return Trueif n == 12:return Falseif n == 13:return Trueif n == 14:return Falseif n == 15:return Falseif n == 16:return Falseif n == 17:return Trueif n == 18:return Falseif n == 19:return Trueif n == 20:return Falseif n == 21:return Falseif n == 22:return Falseif n == 23:return Trueif n == 24:return Falseif n == 25:return Falseif n == 26:return Falseif n == 27:return Falseif n == 28:return Falseif n == 29:return Trueif n == 30:return Falseif n == 31:return Trueif n >= 32:return False小A的理由是反正只有32个数字,不妨这样写。紧接着W君就说了那句“你们每行代码都是对行业的侮辱!”
这就是W君遇到的程序员的水平,能理解要判断的数字是有限个数的,于是就给你写出一大堆if判断来做这件事。
脑子这玩意是好东西,可惜现在很多程序员没有这玩意。
于是W君问干嘛不这样写呢?
def is_prime(n):return n in [2,3,5,7,11,13,17,19,23,29,31]如果n在这个[2,3,5,7,11,13,17,19,23,29,31]列表中,直接返回真,否则返回一个数字在不在质数列表中假难道不香吗?
这时候小A在说,这么牛,难道这是军工级的代码吗?
似乎所谓的“军工级”成了一些程序员眼中高效代码的传说。但是还真不是这么回事,而且上面的那一行代码的效率也并不高,毕竟python要在列表里挨个去试探n是不是在列表中,这个操作只不过是一个for循环的展开。
再高效一些的代码是这样的:
def is_prime(n):return (1 << n) & 2693408940 != 0理论上这是最快的方式,当然了,这仅仅是在理论上,2693408940展开为二进制为:10100000100010100010100010101100,在从低向高的质数位上放“1”,其余非质数位置上放0。我们只需要把这个二进制串直接移动n位就可以对应到是不是质数的判断上。
最多4个CPU时钟周期就可以判断完毕这个数字n是不是质数。
而对于上面的“军工级的代码”查表法则需要大约2000多个时钟周期才可以完成。
但是谁脑子抽了才会用python算这么大的数字来移位计算呢?python根本不擅长这样的操作。
反而,反直觉的操作会更快:
def is_prime(n):return n in {2,3,5,7,11,13,17,19,23,29,31}和前面的代码不同,列表("[]")被改成集合("{}"),也就是把中括号改成大括号。在性能上会比python中的移位的操作快将近一半。
那问题来了——军工级的代码要怎么写呢?
首先军用代码谁去python呢?这玩意狗都不用。大部分军用代码的编写目前还是编译形语言的天下,C语言都用得不多,更为普遍的是ADA。和python这些解释形的语言相比它们更接近于硬件底层、执行效率更高,可控性更强,尤其是ADA,它的语法严谨,天然适合安全关键系统。在咱们国内,大部分航空、航天、飞行控制、舰船控制的代码都是ADA来写的。
其次,军规代码是有自己的规范的。例如MISRA C / MISRA C++、DO‑178C、SPARK/Ada以及MIL-STD-498或者GJB 2786A一系列软件开发标准。
那么到了代码层级,就远远不是前面写了一两行代码这么简单的事情了。
军工级代码和普通商业代码的最大区别,不仅仅是快,而是可靠,不是“能跑”,而是无条件正确。
你可能会觉得这种说法有些装,但我告诉你,军用代码的世界和现在的商业程序猿们写业务逻辑调API、对接数据库的世界完全不是一个宇宙。
- 确定性(Determinism):相同输入下,任何时刻、任何硬件平台都必须产生相同输出;
- 形式化验证(Formal Verification):代码不能只是跑通单元测试,而是逻辑路径必须被证明不会出现“非法状态”;
- 零未定义行为(No Undefined Behavior):你写个野指针在Linux下可能没出问题,在RTOS里就是立即炸;
- 强类型/静态分析支持:例如 Ada 的子类型约束系统,能在编译期就拒绝不合理状态;
- 资源确定性:执行时间、栈空间、缓存访问必须全部可估算,不允许 GC、动态分配、解释器优化等不确定性行为。
与此同时,你的代码还要具有极强的可读性和可验证特性。
以刚刚咱们的代码为例子:
def is_prime(n):return (1 << n) & 2693408940 != 0上面这种商业程序员认为的“军工级代码”实际上根本就是错误的。
原因在于哪个大聪明给你的这个“2693408940”数字,你说从低到高凡是质数的位上都是1就是这个“2693408940”的数字了吗?有没有缺掉某个数字?是不是在原理上没有错误?
“!=0” 这种不是人话的描述给军队的任何人看都能看懂吗?
在真正的军用系统中,上面这种炫技式样的代码不仅不被提倡,甚至是被严令禁止的行为。
2693408940 = 0b10100000100010100000100001001100这确实可以是某位程序员按照 0~31 范围内的质数打出的一串数字位,其中第 n 位为 1 表示 n 是质数。但这东西有几个致命问题:首先这串数字是难以验证来源,你只能“相信”这个数字是对的,而无法“推理”它是对的。军用系统所有值必须可追溯到规范、公式或定义。而这个2693408940的推导过程并没有在软件编码中出现。
同时,这段代码的可维护性为0,如果哪天质数定义范围改了,谁知道这段代码还对不对?战争都开打了,还让数学家来证明和定义具体哪个数字是质数吗?
同样,“!=0”不是语义,而是一个障眼法,军工级代码必须可读、可分析、可验证。“!= 0” 是个机械语言,而不是表达语义。0b10100000100010100000100001001100经过计算非得“!=0”吗?难道就不能直接写真假吗?
所以,如果是一个军工级代码应该怎么写呢?这样
#include #include #define PRIME_TABLE_SIZE 32Ustatic uint8_t prime_table[PRIME_TABLE_SIZE]; /* 0 = false, 1 = true *//* 判断一个数是否为质数(布尔逻辑) */static bool is_prime_check(uint8_t n){ if (n <= 1U) { return false; } for (uint8_t i = 2U; (i * i) <= n; ++i) { if ((n % i) == 0U) { return false; } } return true;}/* 初始化 prime_table[] */void prime_table_init(void){ for (uint8_t i = 0U; i < PRIME_TABLE_SIZE; ++i) { prime_table[i] = is_prime_check(i) ? 1U : 0U; }}/* 验证表正确性 */bool prime_table_verify(void){ for (uint8_t i = 0U; i < PRIME_TABLE_SIZE; ++i) { uint8_t expected = is_prime_check(i) ? 1U : 0U; if (prime_table[i] != expected) { return false; } } return true;}/* 接口函数 */bool is_prime(uint8_t n){ if (n >= PRIME_TABLE_SIZE) { return false; } return (prime_table[n] == 1U);}首先,在代码中并不会存储一个不知所云的数字,例如“2693408940”,而是利用“prime_table_init”函数在初始化的时候生成这个质数列表。同样,在生成后还需要对列表进行验证(prime_table_verify),以确保在生产的过程中出现错误。
其次,所有的内容都是“显式”的,并不会有晦涩难懂的数据和算法,甚至0和1所代表的意义也需要在声明的时候就近写出注释(/* 0 = false, 1 = true */)。
最后,代码看起来冗余度很大,比之前的“一行函数”要复杂很多。但每一行的职责就更明确了。
那么为什么ADA更适合军用代码呢?如果我们把C语言转成ADA就更加一目了然了:
with Ada.Text_IO; use Ada.Text_IO;with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;procedure Prime_Table_Gen is subtype Prime_Index is Integer range 0 .. 31; type Prime_Table_Type is array(Prime_Index) of Boolean; function Is_Prime(N : Integer) return Boolean is begin if N <= 1 then return False; end if; for I in 2 .. Integer(N ** 0.5) loop if N mod I = 0 then return False; end if; end loop; return True; end Is_Prime; Prime_Table : Prime_Table_Type;begin -- Initialize table for I in Prime_Index loop Prime_Table(I) := Is_Prime(I); end loop; -- Output for verification for I in Prime_Index loop Put("Index "); Put(I, 2); Put(": "); Put_Line(Boolean'Image(Prime_Table(I))); end loop;end Prime_Table_Gen;大部分的内容都是平铺直叙接近于自然语言所书写的。在这种状态下即便是一个没有太多程序经验的审计人员也可以很迅速的理解这些代码所代表的含义。这在军用系统中十分重要。
当然了,也正是有各自初始化和验证的过程存在。军用系统的启动往往是这样的:
慢慢的一部分一部分的打开,相当具有“仪式感”。
所以说嘛,要不是知道军用代码是怎么写、是什么样子的,今天上午,还真被人拍马屁拍爽了,哈哈哈。
热门跟贴