【发布时间】:2009-12-17 21:08:02
【问题描述】:
我经常听到人们称赞语言、框架、结构等“明确”。我试图理解这个逻辑。语言、框架等的目的是隐藏复杂性。如果它让你明确指定各种细节,那么它并没有隐藏太多的复杂性,只是移动它。显式性有什么好处?如何使语言/框架/API“显式”,同时仍能实现隐藏复杂性的目的?
【问题讨论】:
我经常听到人们称赞语言、框架、结构等“明确”。我试图理解这个逻辑。语言、框架等的目的是隐藏复杂性。如果它让你明确指定各种细节,那么它并没有隐藏太多的复杂性,只是移动它。显式性有什么好处?如何使语言/框架/API“显式”,同时仍能实现隐藏复杂性的目的?
【问题讨论】:
你应该显式还是隐式取决于具体情况。你是对的,因为你经常试图隐藏复杂性,而在幕后自动为你完成的某些事情是好的。封装等
虽然框架或构造有时会向我们隐藏它们不应该隐藏的东西,这使得事情不太清晰。有时某些信息或设置对我们隐藏,因此我们不知道发生了什么。做出了我们不理解也无法确定的假设。发生了我们无法预测的行为。
封装:好。隐藏:不好。做出正确的决定需要经验。逻辑属于哪里,应该是明确的。
示例:我曾经从一系列十几行代码中删除了大约 90 行代码;不属于那里的数据访问代码、业务逻辑等。我将它们移至基本页面和关键业务对象。这很好(封装、关注点分离、代码组织、解耦等)。
然后我兴奋地意识到我可以从许多这些页面中删除最后一行代码,将其移至基本页面。这是一条从 url 获取参数并将其传递给业务对象的行。好,对吧?嗯,不,这糟糕(我隐藏)。这个逻辑属于这里,尽管它几乎是每一页上的同一行。它将 UI 意图与业务对象联系起来。它必须是明确的。否则我是在隐藏,而不是封装。通过该行,查看该页面的人会知道该页面做了什么以及为什么;没有它,很难确定发生了什么。
【讨论】:
我相信显式是指在你使用它时确切地知道它在做什么。这不同于确切地知道它是如何完成的,后者是复杂的部分。
【讨论】:
与其说 explicit 好(当然密切相关的 verbose 不好)不如说当 implicit 出错时,它是很难说 WTF 正在发生。
用一两年的时间学习 C++,你就会明白我的意思了。
【讨论】:
这是关于表达意图。读者无法判断默认值是由于错误还是设计造成的。明确表示消除了这种怀疑。
【讨论】:
代码比写更难读。在非平凡的应用程序中,一段给定的代码的阅读频率也将高于其编写频率。因此,我们应该编写代码以使读者尽可能容易。做很多不明显的事情的代码不容易阅读(或者更确切地说,当你阅读它时很难理解)。因此,明确性被认为是一件好事。
【讨论】:
依赖默认行为会对不熟悉语言/框架/其他内容的人隐藏重要细节。
考虑一下,对于不了解 Perl 的人来说,广泛依赖简写的 Perl 代码是如何难以理解的。
【讨论】:
显式与隐式都是关于你隐藏什么,以及你展示什么。
理想情况下,您可以公开用户关心或必须关心的概念(无论他们是否愿意)。
明确的好处是更容易追踪和找出发生了什么,尤其是在失败的情况下。例如,如果我想进行日志记录,我可以有一个 API,它需要使用日志目录进行显式初始化。或者,我可以使用默认值。
如果我给出一个明确的目录,但它失败了,我会知道为什么。如果我使用隐式路径,但它失败了,我将不知道哪里出了问题、为什么,或者去哪里修复它。
隐性行为几乎总是对消费者隐藏信息的结果。有时这是正确的做法,例如当您知道在您的环境中只有一个“答案”时。但是,最好知道您何时隐藏信息以及隐藏信息的原因,并确保您让消费者的工作更接近他们的意图,而不是试图隐藏基本复杂的项目。
经常隐含的行为是“自我配置”对象查看其环境并尝试猜测正确行为的结果。我一般会避免这种模式。
我可能总体上遵循的一条规则是,对于给定的 API,任何操作都应该是显式的或隐式的,但绝不是组合。要么让操作成为用户必须做的事情,要么让它成为他们不必考虑的事情。当您将这两者混合在一起时,您会遇到最大的问题。
【讨论】:
框架等通过为要完成的工作提供正确的抽象,既可以显式也可以隐藏复杂性。
明确允许其他人检查和理解原始开发人员的意思。
隐藏复杂性并不等同于隐式。隐含性会导致代码只有编写者才能理解,因为在这种情况下,试图理解幕后发生的事情类似于逆向工程。
显式代码理论上有可能被证明是正确的。隐式代码在这方面永远没有机会。
显式代码是可维护的,而隐式代码则不是——这与提供正确的 cmets 和谨慎选择标识符有关。
【讨论】:
“显式”语言允许计算机发现软件中的错误,而不太显式的语言则不能。
例如,C++ 有 const 关键字用于其值永远不会改变的变量。如果程序试图更改这些变量,编译器会指出代码可能是错误的。
【讨论】:
良好的抽象不会隐藏复杂性,它会做出最好留给编译器的决定。
考虑垃圾收集:释放资源的复杂性被委托给垃圾收集器,它(大概)比程序员更有资格做出决定。它不仅可以让您做出决定,而且可以做出比您自己做出的更好的决定。
明确性(有时)是好的,因为它使得在某些情况下最好留给程序员的某些决定不会由不太合格的代理自动做出。一个很好的例子是当您在 c 类型语言中声明浮点数据类型并将其初始化为整数时:
double i = 5.0;
如果您将其声明为
var i = 5;
编译器会正确地假设您需要一个 int 并且稍后的操作将被截断。
【讨论】:
double i = 5; 或 var i = 5.0;
在使代码的读者清楚你打算做什么的上下文中,明确性是可取的。
有很多例子,但都是为了不怀疑你的意图。
例如这些不是很明确:
while (condition);
int MyFunction()
bool isActive; // In C# we know this is initialised to 0 (false)
a = b??c;
double a = 5;
double angle = 1.57;
但这些是:
while (condition)
/* this loop does nothing but wait */ ;
private int MyFunction()
int isActive = false; // Now you know I really meant this to default to false
if (b != null) a = b; else a = c;
double a = 5.0;
double angleDegrees = 1.57;
后一种情况不容误解。如果有人没有仔细阅读它们,或者没有清楚地理解做某事的可读性较差的语法,或者混淆了整数和浮点类型,前者可能会导致错误。
【讨论】:
在某些情况下,相反的是“魔法”——如"then a miracle occurs"。
当开发人员阅读代码试图理解或调试正在发生的事情时,明确性可能是一种美德。
【讨论】:
框架移动事物的目的是消除代码中的重复并允许更轻松地编辑块而不破坏整个事物。 当你只有一种做某事的方式时,比如 SUM(x,y); 我们确切地知道这将要做什么,没有理由需要重写它,如果你必须你可以,但它极不可能。 与之相反的是 .NET 之类的编程语言,它们提供了非常复杂的功能,如果您做任何事情,除了显而易见的简单示例之外,您经常需要重写这些功能。
【讨论】: