在下一本书 - Robert Martin 的“清洁代码。创建、分析和重构”中指出:
一个函数必须只执行一个操作。她应该做得很好。它不应该做任何其他事情。
进一步,作者问“一次操作”是什么意思,并给出了答案:
如果一个函数只执行 在声明的函数名下处于同一级别的那些操作
,那么该函数执行一个操作。
在示例(下)中,作者在声明的函数名称下解释了一层抽象的函数阶段,我无法理解“一层”的理解。条件块是否确定级别?表达式的逻辑序列?
public static String renderPageWithSetupsAndTeardowns(
PageData pageData, boolean isSuite) throws Exception {
if (isTestPage(pageData))
includeSetupAndTeardownPages(pageData. isSuite);
return pageData.getHtmlО;
}
- 该函数检查页面是否为测试页面。
- 如果是,那么它包括开始和结束块。
- 为页面生成 HTML 代码。
也许有更易懂的例子或解释?
(如果一个构建良好的函数在抽象层次上被精确地实现(如作者所描述的那样),那么这是一个非常重要和深刻的概念,应该理解。)
来自维基百科的数据抽象
通过抽象层,您可以分离您的应用程序。例如,您需要制作一个表单提交页面。您将拥有:
- 连接到数据库(级别 1)
- CRUD 使用数据库(级别 2)
- 提交特定表单(级别 3)
也就是说,您将有 3 个级别,并且您需要,例如,添加到另一个数据库的连接,在哪里实现它?当然,在第一级。如果你需要制作另一个表格,那么在 3 号。
抽象级别由您定义,因此这是一个相当复杂的过程。上面的例子很简单,因为它是教科书。
让我们来看看你的功能。
这个 renderPageWithSetupsAndTeardowns 函数可以有多个名称,具体取决于您构建抽象层的方式。
理论上,它应该被简单地称为renderPage。而作为名称一部分的解析和设置应该处于不同的抽象级别(即,呈现页面的类可以有一个父类,也可以包含一个对 html 页面进行解析组装的对象)。
这完全取决于您如何分解任务。您将选择哪些对象作为基础。您将拥有多少个简单元素。
抽象级别是衡量其详细程度的指标。
那些。如果我们对一个对象进行操作,这是一个抽象级别,如果我们开始爬入它的各个部分并已经对它们进行操作,这是另一个抽象级别,它低于第一个抽象级别。
让我们概括一下。如果函数执行的交互级别相同,则函数执行一项操作。如果在一个运算符中,一个函数调用一个对象,而在另一个运算符中,它提取其部分的方法,那么这是两个不同的操作。
如果有不清楚的地方,请提出问题。
应@TimurVI 的要求,我正在将评论翻译成答案。
你的问题很难明确回答。突出抽象层次是一门艺术。UML 非常适合描述(和可视化)系统。引用:“每个实体都专注于系统的某些特定方面并表达不同的抽象级别。换句话说,每个模型对应于正在设计的系统的某些特定的特定观点。” 或者关于抽象级别的东西
即使在上面的例子中,也不是一切都顺利进行,例如,为什么渲染涉及检查,并且返回一个字符串,而不是直接在对象上调用?这里观察到三个动作——检查、填充、编码成字符串。在我个人看来,检查和填写可以在同一个层次上,但编码是完全不同的任务。可能这本书的作者有更广阔的视野。
一般来说,实体膨胀的问题很常见:代码作者试图将他们可以进入的所有内容都塞进一个类/函数/循环/代码块中,显然相信它会更优化或者应该放在一个地方,我不知道。但是在一年、两年或一周之后,这个本质就被扔掉了——它无法阅读,完全无法理解为什么需要它。因此,基本规则:每个实体都必须针对一个目的,并执行与该目的直接相关的操作。这里的“动作”与其说是类方法(其实类方法也是一个实体),不如说是要执行的指令的含义,它们位于实体的同一(顶层)层级。
例如,渲染。你可以塑造一个接受某物的东西,将其转换成某种东西,填充某个缓冲区,将其转换为字符串并将其发送到某个地方,所有这一切都是一个长代码脚布。或者你可以想一想,让一个实体负责接收数据,一个将数据转换成绘图指令的实体,一个将一条指令转换成图片(draw)的实体,一个按照指令序列填充图片的渲染实体(in事实上,它不绘制),将结果打包成所需格式的实体。
第二种选择比较麻烦,但是每个实体都可以随着项目的发展而被修改、重用或消除,而不是从头开始重写所有内容。
或者在这里。该函数从文件中读取。我们阅读代码:打开文件,检查描述符,将数据读入缓冲区,检查大小,通过可见性检查逐个字符循环输出到控制台,关闭文件。所以!在这里停止输出循环正在做的事情。没错 - 将其移至另一个功能:打印到控制台。总计,打开,阅读,打印,关闭。不完美,但更好。为什么不完美?函数本来的目的就是读取文件,为什么这里要打印呢?该怎么办?最简单的事情是改变目标,最正确的事情是戳“更高”。最后,如果我们的函数返回一个缓冲区(或填充这个缓冲区),这与它的目的并不矛盾,但是如何处理这个缓冲区并不关心它。
我记不太清了,但有人怀疑我们在这里讨论的是只访问传递参数的一级属性。也就是说
isTestPage(pageData)
,它是可能的pageData.getHtmlО
——它也是可能的,但有些pageData.smth.inner
——它不再可能了。