再论返回值
相关阅读 |
---|
初问返回值 |
C++吐槽 |
最终,还是没能学会C++。但一点也不懊丧,甚至有些窃喜。😏
先看几个函数原型
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
int getaddrinfo(const char *node, const char *service,
const struct addrinfo *hints,
struct addrinfo **res);
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
__drv_aliasesMem LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
public sealed class OpenFileDialog : System.Windows.Forms.FileDialog
func fileExists(atPath path: String,
isDirectory: UnsafeMutablePointer<ObjCBool>?) -> Bool
func downloadTask(with request: URLRequest,
completionHandler: @escaping (URL?, URLResponse?, Error?) -> Void) -> URLSessionDownloadTask
返回什么?
长久以来,或说从正式开始学习写程序以来,头脑中一直萦绕着这样一个问题:函数返回值应该是什么?
到如今,经过反复思考和求证、阅读编程老手的代码不断揣度、问各路神通这个问题后,我大致有了如下的一些太长不看的结论:
- 不同时期主流编程语言不同,每个时期有不同风格,没有普适的答案
- 过去由C语言统治的时期,由于不支持异常处理和多返回值,所以多采用返回错误代码errno、通过指针修改传入参数地址处的数据结构来返回的方法
- 在Java流行的年代,值类型逐渐变多,传统风格方兴未艾,单个多个返回值、修改实参、修改类成员、修改全局变量、抛异常等等大杂烩
- 在多核多进程多线程并发的当代,强类型、编译时安全语言与值类型语言逐渐占据优势
- 当代面向人类开发者尽量多用符合语义的单/多值类型返回与异常处理;面向机器无所谓
- 用好Optional
- 原先的参数返回与返回值返回的考量,变成了现在同步值返回与异步回调的取舍
- 函数式编程的思想会卷土重来
第一阶段
如引用和附录所言,自己第一次正经学编程语言,是在大一时学的C语言程序设计课上。中学虽然用过一些脚本和VB6.0,但都只是玩票,对于数据结构和算法完全没有认知。学完本科的C语言课程后,在很长一段时间里,我的编程思维都是面向过程的、C风格的,没法写出任何稍微复杂的需要有面向对象思维的程序。就拿本科写的最复杂的几个程序来说:MATLAB计算波导磁场、Verilog调制解调、LabVIEW信号采集。它们大多数都像是将数学公式和函数,或是一个操作过程用程序语言描述出来,只需要简单的control flow即可实现“自动化”。
那时非常常见的函数原型:
void foo();
完全就是用一个函数把一坨逻辑打包装起来,在需要的时候调用罢了。现在看来,这种所有语句全都堆在main.c
里面的程序,完全可以用goto
甚至汇编的je
来实现。那时对于返回值基本相当于没有概念。
第二阶段
2017年9月第一次学正经的“工业语言”C++。说是正经学,实际上一知半解。对于面向对象的“封装继承多态”完全没有概念,重载和重写傻傻分不清。甚至连函数指针都唯恐避之不及。最后结果也相当凄惨,在献祭了人生第一次通宵后依旧备受打击。
对于工程、编程乃至工业的方方面面,我一直以为,如果有一个东西普通人学不会,那一定是它设计存在缺陷。C++便是这样一件事物。学习编程,应该是学习编程的思想,而不是在学习语言的语法。如果一个语言本身能够成为一种“技术壁垒”,或许它的未来会像许多前辈如COBOL、Fortran一般。
编程在21世纪会是泛在的。所有具有基础读写能力的年轻人都应该能够编程,至少能够输入逻辑让计算机完成特定的任务。当然,这些和返回值没啥关系。
写C++时对返回值更加疑惑了。写多态运算符重载时用的那一套符号让本就近视的我雪上加霜。事情本质很简单也很清晰,又为什么要用一堆非得按shift的符号来描述呢?从C过渡到C++时,逐渐产生了返回值的疑问。那时接触的C语言程序都不复杂,直接返回值,返回个指针或者不返回都很正常。但C++能返回实例,传参又能传引用,一下子就把我搞蒙了。程序一次跑过不难,valgrind一次不红可是难上加难。实话实说,到现在我也基本不理解RAII的用法,更别提写出对于细粒度内存管理完全自信的程序了。嗨,这些玩意,现在看来,难道不该让计算机自己完成吗?
川当时用Java刷题近300道,第二次学C++,551期末考了A+。他的一句话让我记忆犹新:
都2017年了,谁™还用指针啊。
大家会心一笑。后来我才知道Java连malloc都不用,更别提指针了。当然这些都是后话。
第三阶段
2019年6月的某一天,是我视作正式“转码”的日子——那天,我打开LeetCode,做了第一道题:Two Sum。第一次提交的代码——
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
vector<int> result;
int index1 = 0;
int index2 = 1;
std::vector<int>::iterator it1 = nums.begin();
std::vector<int>::iterator it2 = nums.begin() + 1;
while (it1 != nums.end()) {
while(it2 != nums.end()) {
if (*it1 + *it2 == target) {
result.push_back(index1);
result.push_back(index2);
return result;
}
else {
++it2;
++index2;
}
}
++it1;++index1;
it2 = it1 + 1; // reset the position
index2 = index1+1;
}
};
};
虽然一个五六行就能搞定的代码写了近30行,但那已经是我当时C++的最好水平了。有一段时间,自己在各种风格之间摇摆不定,从这个文件中可见一斑。缩进用tab还是空格、C++四格缩进、动词命令式函数名、名词函数名、“残疾”函数原型、C++和C风格注释混用。虽然难受的一屁,但“臣妾实在做不到啊”!那时自己缺少整体的编程素养,注定是写不出健壮的代码的。但就像之前所说,“赛跑”逼迫着转码选手“赶鸭子上架”。很多朋友就是靠着Java刷题顺利进入了心仪的公司。而且我相信这些大公司一定也有代码规范能让他们迅速成长起来,开发出风格统一的代码来。因此,当时还不如放下纠结无脑随大溜呢!
有一段时间“能不返回就不返回”,尽可能地遵照C风格的返回错误代码,指针传递结果的写法来写。较复杂的函数还好,一些简单的函数如strip(),都这样搞实在费劲。看着函数里一排指针就头疼!写C++后则遵照能不在子函数new就不new的写法来写,在main函数里把所有需要的内存全申请了,这总该可以了吧——引用传来传去还是头疼!后来只好随心所欲,遵循“想返回什么就返回什么”的写法。虽然写起来自然,但重看重构代码有时觉得以前的设计不好,也很纠结。没法子,上论坛发帖,结果发现这个问题确实没有定论。唉,还是得自己慢慢摸索!
再看上面的函数
比较熟悉的几种编译时语言的一些常用系统函数。反映了我的心路历程。
- 开始写稍微复杂的C程序时,光是读man page就要读到头痛。无数的自定义结构体、传来传去的指针、未定义的返回错误代码。再加上字符串……还是只让C做它的本职工作好了。
- 写奇怪的平台限定C++程序时:啥啥啥,这些玩意都是啥?如果MATLAB程序的库函数返回一个“handle”、句柄,大部分时候权当做空气无视掉。返回值看不懂不如看看参数。这些参数又是啥?没一个基础值类型,也不是标准库的数据结构。不如再来个
LPCWSTR const wchar_t*
图一乐。 - C#终于回到了人类世界。真的,第一次写C#程序时,虽然完全不懂它的语法,但还是莫名地有了被微软温柔以待的感觉。风格沿袭C语言,而变得简明。不再纠结写点还是箭头,自动补全也比Sublime手撸C++畅快了许多。虽然文本编辑器也能补全,但预置没法一键查文档,查调用,还是比不上“宇宙第一”IDE来得方便。
- Swift终于让我确信自己不是智障,甚至有了些自信。清晰、简明、一眼看去便知函数在做什么;返回值、参数、异步调用赫然在目,寥寥数语便能安全地把远隔重洋的数据下载到本地。这种对于程序运行结果的完全掌控感,是其他语言难以简单获得的。
或许下一步该看函数式语言了。
瞎比拟
- C++像清真寺、Swift像麦当劳
C++是一种宗教,g++和MSVC++就像mantis与crystal;98和11是两门语言;17和21语法甚至让11的程序员看不懂。多像旧约新约和后期圣徒呀。
Swift则不然。流行、方便、普世、干净整洁标准化、质量中上。就像快餐品牌麦当劳。走遍全世界,或许有细微的差异,但大体上体验是相同的。这种可预知性,在战火纷飞瘟疫横行的年代,是多么难能可贵呀!走进了Swift的店面,那些叫嚷着追击的memory leak和race condition僵尸,被安全地阻挡在防弹玻璃门之外。
- 自由与专制:从C到Java
C是自由的语言。如果汇编语言的复杂度上限是WPS这类软件的话,那C的海洋则是无限宽广。libc的泛在程度,或许比我眼前所见的任何一件事物都要高出几个数量级。
没有用过Java,在可预见的未来不会用Java。这里放一张图代表我的观点。
- 设计者 vs 开发者
喜欢Swift的另一个原因:自己更多以设计者而非开发者自居。小时候最想当architect;长大后发现,虽然自己很难成长为优秀的设计师,但在生活中始终保有设计感、对设计保持敏锐的观察力非常重要。
Swift令设计人员摆脱程序编写的困扰。极其方便且安全地验证设计是否合适,而不必纠结细节;丰富、优秀且引领潮流的交互设计让创意与实现的距离不再遥远。
- 程序设计语言的未来
新一代程序设计语言是人们对PL理解的里程碑——多数问题应该由机器自己、编译器解决,而非让初级开发者如履薄冰、举步维艰。
程序语言本身不应该是开发的门槛。
程序语言应该是简洁、健壮、快速而安全的。
从这个角度来看,工作中自己将会长期使用Swift;希望学习的下一门语言是Rust。C#是好,却是微软限定的好。这个层面上,更信任工程师而非科学家开发出的语言。
现代工具非常爽。庆幸于从开始学代码就用有现代工具的语言,而非那些艰深的、无法开发统一“清洗”工具的语言。linter让我少走了很多很多弯路。CI和Git让我避免了拷贝”桌面备份.zip”到机房的尴尬。
- 代码风格
虽然这里扯了不少,但关于这个话题总有说不完的唠叨。以后再说。
但愿自己编程营生的生涯不要太长;总能写出学生标答风格的代码:结构优美、不一定是最简洁one-liner、最高可读性、不让计算机运行最快、没有黑魔法。让审计者犹如阅读高考题目答案一般,四平八稳挑不出毛病,也读不出天赋的痕迹。
不让计算机运行最快:或许可以写一篇关于“缓慢程序主义”的文章。如果有一天人工智能真的反噬人类,那么至少我做出过小小的抵抗——在合理的时间复杂度下,尽可能多地牺牲性能以换取对人的友善程度,不仅限于可读性和安全的错误处理。让计算机可控地慢下来。
在C和Swift里,想快就快想慢就慢。这样的迟缓是可控的。但对于C++,始终没能掌握合理地使程序慢到特定节奏的方法。
- 优越感?
Java和C++14-历史包袱太过沉重,是典型过度工程的结果。相比之下,C经历了风云变幻,却依旧历久弥新,实乃上乘。
除非特定任务或给足钱,否则坚决不碰Java。看看Minecraft的混乱吧 xxUtils
希望自己的编程维生的生涯,在必须触碰Java前结束。
Leave a Comment