14

我经常听到人们称赞语言、框架、结构等是“明确的”。我试图理解这个逻辑。语言、框架等的目的是隐藏复杂性。如果它让你明确指定各种细节,那么它并没有隐藏太多的复杂性,只是移动它。显式性有什么好处?你如何使语言/框架/API“显式”,同时仍使其达到隐藏复杂性的目的?

4

13 回答 13

30

你应该是显式还是隐式取决于具体情况。你是对的,因为你经常试图隐藏复杂性,而在幕后自动为你完成的某些事情是好的。封装等

有时,尽管框架或构造对我们隐藏了它们不应该隐藏的东西,这使得事情变得不太清楚。有时某些信息或设置对我们隐藏,因此我们不知道发生了什么。做出了我们不理解也无法确定的假设。我们无法预测的行为会发生。

封装:好。隐藏:不好。做出正确的决定需要经验。逻辑属于哪里,它应该是明确的。

示例: 我曾经从一系列十几个页面后面的代码中删除了大约 90 行代码;不属于那里的数据访问代码、业务逻辑等。我将它们移至基本页面和关键业务对象。这很好(封装、关注点分离、代码组织、解耦等)。

然后我兴奋地意识到我可以从许多这些页面中删除最后一行代码,将其移动到基本页面。这是一条从 url 获取参数并将其传递给业务对象的行。好,对吧?好吧,不,这很糟糕(我躲起来了)。这个逻辑属于这里,尽管它几乎是每一页上的同一行。它将 UI 意图与业务对象联系起来。它需要是明确的。否则我是在隐藏,而不是封装。通过该行,查看该页面的人会知道该页面做了什么以及为什么;没有它,很难确定发生了什么。

于 2009-12-17T21:15:11.107 回答
15

我相信显式是指在您使用它时确切地知道它在做什么。这不同于确切地知道它是如何完成的,这是复杂的部分。

于 2009-12-17T21:11:51.263 回答
8

与其说显式是好的(当然密切相关的冗长是坏的)不如说当隐式出错时,很难判断 WTF 正在发生。

破解 C++ 十年或两年,你就会明白我的意思。

于 2009-12-17T21:40:25.173 回答
7

这是关于表达意图。读者无法判断默认值是由于错误还是设计造成的。明确的消除了这种怀疑。

于 2009-12-17T21:16:34.863 回答
6

代码比写更难读。在非平凡的应用程序中,一段给定的代码的阅读频率也将高于其编写频率。因此,我们应该编写代码以使读者尽可能容易。做很多不明显的事情的代码不容易阅读(或者更确切地说,当你阅读它时很难理解)。因此,明确性被认为是一件好事。

于 2009-12-17T21:14:18.063 回答
4

依赖默认行为会向不熟悉语言/框架/其他内容的人隐藏重要细节。

考虑一下,对于不了解 Perl 的人来说,广泛依赖简写的 Perl 代码是如何难以理解的。

于 2009-12-17T21:10:43.547 回答
4

显性与隐性完全取决于您隐藏的内容和显示的内容。

理想情况下,您公开用户关心或必须关心的概念(无论他们是否愿意)。

明确的好处是更容易追踪和找出发生了什么,尤其是在失败的情况下。例如,如果我想进行日志记录,我可以有一个 API,它需要使用日志目录进行显式初始化。或者,我可以使用默认值。

如果我给出一个明确的目录,但它失败了,我会知道为什么。如果我使用隐式路径,但它失败了,我将不知道出了什么问题,为什么,或者去哪里修复它。

隐性行为几乎总是对消费者隐藏信息的结果。有时这是正确的做法,例如当您知道在您的环境中只有一个“答案”时。但是,最好知道您何时隐藏信息及其原因,并确保您让您的消费者更接近他们的意图水平,而不是试图隐藏基本复杂的项目。

通常隐含行为是“自我配置”对象查看其环境并尝试猜测正确行为的结果。我一般会避免这种模式。

我可能总体上遵循的一条规则是,对于给定的 API,任何操作都应该是显式的或隐式的,但绝不是组合。要么让操作成为用户必须做的事情,要么让它成为他们不必考虑的事情。当你把这两者混合在一起时,你会遇到最大的问题。

于 2009-12-17T23:32:48.780 回答
3

框架等通过为要完成的工作提供正确的抽象,既可以显式也可以隐藏复杂性。

明确允许其他人检查和理解原始开发人员的含义。

隐藏复杂性并不等同于隐含。隐含性会导致代码只有编写者才能理解,因为在这种情况下,试图理解幕后发生的事情类似于逆向工程。

显式代码理论上有可能被证明是正确的。隐式代码在这方面永远没有机会。

显式代码是可维护的,而隐式代码不是 - 这链接到提供正确的注释并小心选择您的标识符。

于 2009-12-17T21:17:13.003 回答
2

“显式”语言允许计算机发现软件中的错误,而不太显式的语言则不能。

例如,C++ 有一个const关键字来表示其值永远不会改变的变量。如果程序试图更改这些变量,编译器可以声明代码可能是错误的。

于 2009-12-17T21:17:21.377 回答
2

在使代码的读者清楚您打算做什么的上下文中,明确性是可取的。

有很多例子,但这一切都是为了不要怀疑你的意图。

例如,这些不是很明确:

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;

后一种情况没有任何误解的余地。当有人没有仔细阅读它们,或者没有清楚地理解做某事的可读性较差的语法,或者混合了整数和浮点类型时,前者可能会导致错误。

于 2009-12-17T21:19:23.233 回答
2

好的抽象不会隐藏复杂性,它会做出最好留给编译器的决定。

考虑垃圾收集:释放资源的复杂性被委托给垃圾收集器,它(可能)比程序员更有资格做出决定。它不仅可以让您做出决定,而且可以做出比您自己做出的更好的决定。

明确性(有时)是好的,因为它使得在某些情况下最好留给程序员的某些决定不会由不太合格的代理自动做出。一个很好的例子是当您在 c 类型语言中声明浮点数据类型并将其初始化为整数时:

double i = 5.0;

相反,如果您将其声明为

var i = 5;

编译器会正确地假设您想要一个 int 并且稍后的操作将被截断。

于 2009-12-17T21:20:21.167 回答
1

在某些情况下,相反的是“魔术”——如“然后奇迹发生”

当开发人员阅读代码试图理解或调试正在发生的事情时,明确性可能是一种美德。

于 2009-12-17T21:13:32.480 回答
0

框架移动事物的目的是消除代码中的重复,并允许更轻松地编辑块而不破坏整个事物。当你只有一种做某事的方式时,比如 SUM(x,y); 我们确切地知道这将要做什么,没有理由需要重写它,如果你必须你可以,但它极不可能。与之相反的是 .NET 之类的编程语言,它们提供了非常复杂的功能,如果您做任何事情,除了显而易见的简单示例之外,您经常需要重写这些功能。

于 2009-12-17T21:15:17.627 回答