关于软件设计的一些问题

2022-02-22 21:41:53 +08:00
 lanlanye

不知道各位写代码时会不会经常觉得遇到以下问题:

  1. 某个听起来很自然的功能实现起来却需要绕很大一圈。
  2. 某个功能的实现不可避免的会影响到许多不相关的功能。
  3. 迭代到某个阶段,软件的响应越来越慢,逻辑越来越复杂,开发人员已经搞不懂自己在做什么了。

...

以上只是我遇到许许多多问题中的一小部分,毕业快两年,我时常觉得自己写出的东西随着功能的增加离自己的设想越来越远,即使满足了业务需要也无法让自己满意。

我认为开发难以进行的根源往往不是因为开发人员不会写业务代码,而是设计上出现了不合理的地方,有时是因为初期没有时间仔细考虑,有时是因为不可避免的需要与其他项目耦合,然而归根结底是开发者的设计能力不足,没能让软件保持低耦合与高内聚。

近一个多月的时间,我试着通过各种渠道补充相关知识,从 数据库设计的一个小问题 到流行的架构模式和设计模式逐步学习,深感从 Python 开始开发一定程度上让我低估了面向对象设计的重要性。

以下是我最近读过或正在读的书单(按时间顺序):

  1. 《数学之美》
  2. 《 SQL 反模式》
  3. 微软公开的 Azure 设计模式说明
  4. 《实现领域驱动设计》
  5. 《设计模式》
  6. 《领域驱动设计·精简版》
  7. 《领域驱动设计·软件核心复杂性应对之道》

顺序上存在一些不合理的地方,比如《实现领域驱动设计》中提到了许多我难以理解的概念,这些概念是通过穿插着阅读后面几本书以及通过搜索引擎才逐渐能够理解的(以及我至今也没能把这本书彻底读完),这些书中也会引用其他人的著作,受限于时间我也没能逐个阅读,比如《分析模式》、《重构》等。

可是直至目前我依然有很多疑问:印象里不久前还经常被人提起的“函数式编程”有什么优势?如果面向对象设计是一种“最佳实践”,为什么后来诞生的语言没有特别强调自己面向对象的特性?

简言之,我隐约感觉自己遇到了瓶颈,我不确定这些疑惑是否会随着不断学习而得到解答,同时也很担心自己绕了远路钻了牛角尖,所以 希望站内的大佬们可以推荐一些相关书籍或者分享一些自己的学习经历、见解

另外,我建了一个 QQ 群,希望 和我有同样困惑或相似目标 的人能够加入进来共同交流进步。这是一个尝试,我希望它是一个单纯的技术交流群,没有日常寒暄和无意义水群,不会因为过多的消息被人屏蔽,如果实在做不到我也可能解散掉它,群号 OTAyMzE0NDEz

3543 次点击
所在节点    程序员
44 条回复
liuhan907
2022-02-23 01:28:45 +08:00
@3dwelcome
那听起来不就是 erlang 的 actor 模型的弱化版,何况大规模下几万个指令和几万个函数的理解成本没有什么区别。或者说你觉得 win32 的那套消息机制会比现在的异步等待或者协程更容易理解?我反正不这么觉得。
Leviathann
2022-02-23 01:44:43 +08:00
面向对象在上世纪末是很时髦的东西,很多语言都会说自己“一切都是对象”就跟现在很多语言说自己“函数是一等公民”一样

我觉得这波函数式的火热 react 占了很大的功劳
声明式的 UI 开发告诉大家,过去大家认为没有继承(基于 class 的面向对象区别其他范式最核心的特性)就很难开发 UI ,实际上靠组合也可以做到
然后大数据方面 scala 也炒作( hype )过一段时间
xuanbg
2022-02-23 08:19:15 +08:00
楼主说的这些问题大多是数据模型的问题,数据模型的设计问题里面尤其突出的是业务数据和基础数据不分家的问题。说到底还是抽象不够的问题。
3dwelcome
2022-02-23 10:36:14 +08:00
@liuhan907 "何况大规模下几万个指令和几万个函数的理解成本没有什么区别。"

设计指令的目的,都是为了在不同维度下,让开发体验保证一致。

拿股票 K 线举例,你短线操作会观测到的细节,对于长线波段是没什么价值的,把细节抹去或者隐藏,才能让你更专注到更大的盘面,始终保持掌控力。

你封装函数,封装对象,同样也是为了隐藏细节。这帖子就是为了讨论宏观把控力,协程这些真的都是很枝末细节的部分。

海量函数能不能被很好的管理,要看开发者的经验。但指令模式,一定是可以用代码方式,科学维护管理的。
liuhan907
2022-02-23 11:11:04 +08:00
@3dwelcome
为什么你会认为指令比函数更容易维护?
FrankHB
2022-02-23 11:36:00 +08:00
因为这是显然的倒退。

函数(function) 的另一个同义词是“功能”,本来就有更高层次的抽象的意象在里面。所以编程语言发展过程中会把过程(procedure) 自觉合并到“函数”里,而不管数学上原本的定义是怎么样的。

(另一个原因是数学上的定义本来就不怎么样,混乱程度参考 en.wikipedia.org/wiki/History_of_the_function_concept 词条——看,都需要单独整个条目出来了。)

所以你所谓去函数,会被一般用户理解为去除功能上的模块化抽象。

另一方面,对有点兴趣关心不同语言设计的用户,你说的函数具体是什么,都是个问题。但是既然你用“命令”代替“函数”,那我就当作最常见高级语言里允许带变量名构成参数的那种。

而这样你就走进死胡同里了:你在对抗最常规的高级语言普遍需求——抽象。

学术一点讲,虽然我数落数学定义,但是现代意义上的真正算跟高级语言捆绑在一起的“函数”,其构造器的学名(来自λ演算)就叫“λ抽象”。这货的重要性在于,它是所有日常通用编程语言中叫“函数定义”的东西在操作语义描述中的原型,不管语言的设计者是不是意识到了这点。

为什么命名为抽象?因为它提供了把变量绑定作为形式参数的现代意义上的“函数”的普遍功能,而变量名允许用户自行随意决定而原则上不改变程序的语义稳定性(α-转换),或者说,允许定义函数的作者以决定变量名的方式夹带私货,隐藏实现。配合抽象的应用(函数调用)的结果能被作为抽象的操作数(函数参数),以此能组合出最基本的接口和实现分离提供微观上的可维护性——这是实用意义上最基本的可编程性的一部分。

因此即便原版λ抽象也不支持副作用(以之后几十年的时髦话来说,纯函数式),拿来给人编程,在很大程度上就足够比任何不提供抽象的语言好用。

相比之下,大多数传统数学家(不算整数理逻辑之类的)习惯的那种所谓的函数更适合叫“映射”,严格定义(例如通过笛卡儿积或者数学关系)上只是一种更一般意义上的函数的没多少可编程性的实现方式罢了。

完全没有限制的抽象可能会被滥用,所以也被一些用户批判,比如 FORTRAN 和 BNF 之父 J. Backus 。他在 1977 年图灵奖演说中发牢骚并提出要以所谓的 FLP(function level programming) 解决当前语言的哲学问题。这个方案的核心就是消灭抽象——带变量的函数,让所有函数回归到没有变量而通过组合的更接近传统数学上容易排除私货而更容易推理的玩意儿,所谓 point-free style ( point 就是参数名)。

但这显然没有工程意义上成功到哪去。甚至学术上也不咋地,因为计算模型上撑死就只是μ-递归函数这套,实在没多少新东西。这番推广倒是便宜了一字之差的 FP(function programming) ——而后者是显然接受甚至鼓励λ抽象的。

FLP 失败的根本原因是,禁止用户使用函数参数就是“不好用”。(考虑基本没什么人用,具体现代例子还真不太好举例,比如想想只准管道不准 xargs 之类的 shell ……)要克服 FLP 风格的抽象困难特别是不容易表达常规递归算法描述的解的问题,用户程序基本得像早期 Lisp 语言一样要发明动态的环境(实际通常不具备像λ演算一样用替换实现“调用”的条件,至少非纯函数不大行)甚至词法闭包去重新实现抽象;考虑到实现λ抽象是λ演算的语义上的工作量的 50%以上(这保守了,实际一般起码 80%,如果考虑类型系统等其它特性经常对函数定义有逻辑依赖的话),这四舍五入基本上就是自己实现一个新语言的解释器了。而 FLP 语言又不是汇编这样能容易让硬件支持来换取存在感的语言,在这里还不如直接从实现栈里一脚踹开,站茅坑不拉屎在实用上是完全不合格的。

再者,强调 point-free 也不是什么新的东西,组合子逻辑在 1950 年代早就整出来了——但也就是更早(不晚于 1940 年代)的λ演算上抠掉自由变量的缩水版整理出的(虽然组合子本身的历史可以追溯到 1920 年代)。而使用体验呢?除了纯函数式那套,比汇编还汇编(连寄存器都没)。甚至实际可用的组合子逻辑实现如 unlambda ,为了写出和“传统”语言功能等价的代码的一般方法,还得指望用户自己先构造λ抽象然后人肉编译去掉其中的具名参数。另一种路线是 APL——函数命名炸了(自行查找代码怎么写的)。

这样的语言,能被多数高级语言用户接受才有鬼了。

这里的缩水版函数(组合子),与其说是大道至简的 basic building block ,倒不如说只是一种(通过抽象这种构造器引入的)健全版函数的丐版,甚至都未必有传统数学上的映射实用(映射的描述是起码可以选配抽象的,只是形式上很不严格);毕竟计算模型上考虑语义而不仅仅是表面的语法,和组合子同等重要甚至更根本的东西多了去了。

而所谓的“命令”,不说是不是被抽象养刁了的编程语言用户接受(大部分还是非纯函数式语言的抽象,组合子都没法这样扩展),连学术上引起的兴趣都远不如组合子。

考虑历史,“命令”本质上是什么呢?其实就是因为一开始实现困难,而不那么强调组合的低级语言中间产物——命令或者说指令,早期基本就是机器操作助记符的同义词。之后即便在 FORTRAN 之类的高级语言中推广,但是增加的功能也相当抠,说白了是另一种风格的丐版函数。和组合子的丐版不同在于,它没 point-free 那么极端地拒绝使用不能作为函数组合的参数,但也没一般编程语言强调允许递归地把结果作为实际参数。结果就算不像组合子在高级语言可编程性上那么反人类(还得人肉编译才能写出多数编码),就是鸡肋中的战斗机。

至于为什么讲到 DSL 看上去更容易接受“去函数”?因为 DSL 的目标用户通常没有经过基础的编程语言训练——不但没编程语言理论(大多数程序员其实也都没有),而且缺乏任何一种支持不丐版函数的通用编程语言的学习,所以理解不了“函数”的现代内涵,会把“函数”的理解自觉停留在中学阶段那种漏洞百出的玩意儿上,更加想象不出“函数”能用到怎么灵活的程度。这样,“命令”这种看上去无关的强大的“实用”货色,才会容易被接受。

(其实内部鄙视链也是有的,比如因为解决不了 funarg 问题等实现原因而无法支持一等函数的类 ALGOL 语言和拐弯用“方法”支持自由函数的所谓面向对象语言的用户,就经常在所谓“函数式”语言的用户的火力范围之内。)

对任何有点通用目的编程语言使用经验的用户来说,对不那么丐版的函数(包括“过程”)有点体验就会食髓知味,自然回不去被强迫使用“命令”了。(这也是为什么 shell 不受很多程序员待见的原因之一;虽然讲道理 shell 的函数没那么丐,还有个原因是不容易完全写对,维护起来太作妖了。)

所以你的“微观上是函数,宏观上就自然升级到了指令”根本是外行人^n (n>=2) 的无稽之谈。

(就不说 CPU 指令到底是不是微观的问题了,反正大部分用户都不会直接碰这个去编程。)
FrankHB
2022-02-23 11:38:11 +08:00
淦,上面的 at 怎么掉了……26L Re: @3dwelcome .
3dwelcome
2022-02-23 11:50:27 +08:00
@liuhan907 "为什么你会认为指令比函数更容易维护?"

你要管理一整套基于函数的语法树,构建 AST 是相对复杂的。而程序化去提取指令,要简单太多。

你可能没接触过项目遗留屎山,就是函数和代码量多到,你怎么都改不过来。有时候并不是谁好谁坏的问题,是你看问题的角度不同,舍取的问题。

宏观对我来说,就是把函数细节抹掉,把精力省下来,留到更有意义的事情上。
3dwelcome
2022-02-23 11:53:53 +08:00
@FrankHB 我们说的都不是一回事,你是说哪一种代码实现方法更好,可我说的是整体设计方向。

一个是低维,一个是高维,两者没办法放一起对比的。

你细节 API 代码只要花时间,最后怎么都能写好。但是管理海量代码,是需要一点技巧和模式的。
FrankHB
2022-02-23 11:58:33 +08:00
当然这里大部分问题不是 PL 问题能解决的,而来自工程需求的本质困难。光是需求理解的复杂性( NPL:喵?)指望自动解决就差不多是在指望发明强 AI 。
但还是有点相关的:建议参考 Actor model 和 Scheme 的早期历史。
铜币不够先不展开了。
3dwelcome
2022-02-23 12:23:48 +08:00
K 线是一维的,解释不了宏观和微观,我就用二维图片来代替。

一张堆满细节的超高清图片,在普通的 1080p 显示器上全图显示,细节是自动被抹去的,所谓的下采样。

细节一直存在,但是你看不见,你也不在乎,代码缩放也是这个道理。

放到最大,函数能看见。缩到全图,就自动退化成一个点了。这时候所谓函数名,函数参数都都不重要,用指令包裹起来,我只要发布指令和命令,软件能正常运作,就足够了。
lanlanye
2022-02-23 12:52:42 +08:00
@Leviathann 实际上我也不是很认可 Java 这种强迫用户使用类定义操作的方式(据说后来的许多框架本质上都让用户 绕过了这个限制),但如我题中所说,我是从写 Python 的习惯出发解决问题,最后发现使用 OO 的思想对问题建模是很有必要的,或者本质上来说对问题进行合理抽象是必要的,这里是否应该认为 OO 和 FP 只是不同的抽象工具,其目的是相同的?

@xuanbg 数据模型反映的是业务模型,只有设计出合理的业务模型,才能设计出合适的数据模型来,我觉得这其实是同一个问题。

@FrankHB 感谢科普,虽然和我的问题关系不大,但是了解到了很多新知识。

@3dwelcome 我觉得你们对 “函数” 和 “命令” 的理解可能存在出入,讲的不是一件事情。
charlie21
2022-02-23 13:32:55 +08:00
项目管理三要素:time, cost, scope
一个(设计 / 操作 / 安排)必须匹配你的时间估算和工期计算

抽象度过高的优点是 “满足了更大 scope ” 的情况,缺点是 如果实际上没有那么大的 scope 那么这是对工程资源的浪费

何时抽象等于浪费?就是此时
何时抽象不等于浪费?重构的时候,重新考虑所有问题,重新抽象

那么一个显而易见的结果是
1 第一版的代码总是很烂。这没有关系,这反而是好设计,是匹配当时工期计算的设计
2 第二版的代码里会出现各种设计模式 / 抽象等级比第一版高

但根据地球人类的一些经验,“第二版代码” 往往是不会出现的,是胎死腹中的。理由是:重构需要人力,而重构结果仅仅是和第一版代码(能运行)的运行效果一样。那么还重构什么呢?只有那些 long live 的项目,才有 “好心人” 来运用抽象能力给出第二版代码。这样的项目占总体项目的比例可能不到 10% ,而正是这些人总结出了设计模式这回事:写给参与 long live 项目的人 如何去重构代码。其他人:1 心安理得围绕抽象程度很低的第一版代码做事 2 去给那些愿意给第二版代码付钱的人做事,只有这些愿意付钱的人会让第二版代码不胎死腹中

it's nothing wrong to just let the shit die.
charlie21
2022-02-23 13:39:10 +08:00
当然可能你会又说到 “程序员的成长” 了。不,你没有成长。对公司项目,拿钱办事走人,就这么简单。这个行业不允许你成长,公司不需要你的成长,没什么好成长的。有时候自己刻意运用一些精妙技巧写出的一个非常小的 demo 都比那些公司项目里用大白话写的垃圾的价值高,这就是事实。你的成长需要成本,公司不会为你的成长付费。你不要指望在公司项目里成长,你做梦。
liuhan907
2022-02-24 13:45:06 +08:00
@3dwelcome
我实在很难赞同你对於指令的看法,函数和指令本来就是一回事,都是对过程的抽象。指令可以屏蔽细节,函数当然就也可以做到。我很难理解为何你会觉得指令是比函数更好的抽象。这根本就是换汤不换药。
3dwelcome
2022-02-24 14:14:01 +08:00
@liuhan907 你是小兵的时候,“函数”就是具体执行任务的方式。

你当领导的时候,你自然就不需要关心具体实现细节,只需要给别人发布模糊“指令”即可。

我还是那句话,不同的维度,看到的世界不一样。两者并不能拿来横向对比,孰优孰劣。

好领导就不应该死抠软件实现细节,应该把控大局。
3dwelcome
2022-02-24 14:18:41 +08:00
还有我一直觉得软件开发人员,是需要分层的。

分写 API 和调用 API 的人,不分层很难把软件架构真正做好。普通人不可能把控每个细节,特别是项目里,很多函数不是你写的时候。

一个庞大工程的总代码量,是你无法控制的,分层却是可控的。
RickyC
2022-02-24 17:04:39 +08:00
这没一大段叫人怎样阅读?
应该第一句话就说重点。让人知道你问题的点是什么。
0x0208v0
2022-02-24 17:58:21 +08:00
最近遇到了相同的问题,请教楼主,CRUD 的时候怎么对业务建模。如果实践的话,CRUD 和 API 接口怎么分层,跪求指导
Gota
2022-02-24 21:40:48 +08:00
我过年时发过一帖,推荐有助于突破瓶颈期的书籍,可以参考:
https://www.v2ex.com/t/831291

也可以看看 HackNews 上相关的讨论:
https://news.ycombinator.com/item?id=30228261

至于如何保持对程序的掌控力,我觉得重点在于精通模块化设计。
而面向对象或是函数式编程只是实现模块化的手段。

这方面推荐读读看 《 UNIX 编程艺术》,我简要提一下其中与模块化设计相关的点。

1. 设计良好的程序一定要做到 “机制” 和 “策略" 分离
2. “机制” 偏底层,设计时要做到:1 )相互正交; 2 )稳定不变
3. “策略” 偏上层,保持灵活,有时甚至可以交给用户自己实现
4. “机制” 和 “策略” 之间用一层尽可能薄的胶合层连接

至于具体如何做到上面四点,就到书中找答案吧。

最后关于编程范式,我的习惯是:
1. 偏上层的业务操作特别是 UI ,面向对象
2. 偏底层的机制,面向过程
3. 需要做流式处理,就上函数式 (比如后端数据管道,或是前端的事件流 )

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/835769

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX