我很难尝试用许多分支重构一些代码片段。有许多if / then / else块,其中一些是嵌套的。
是否有任何可用于重构代码的技巧而不会浪费大量时间尝试首先理解该功能的每个次要方面?
现在我基本上使用布尔代数(De Morgan定律)。我正在尝试修改if语句中的条件,因此我可以在if / then / else块之外弹出小代码片段。这有点帮助,但代码仍然是分支的。我知道在某些时候我最终必须将biggies分解为更小的类方法,但这很复杂,因为代码包含对许多其他类方法的调用并且有很多局部范围变量,所以我将不得不处理传递新方法的许多论据。我想知道在开始将它分成不同的小块(类方法)之前,我是否可以使用其他一些技巧来提高代码质量。
答案 0 :(得分:14)
根据我的经验,在进入分支和等效的逻辑表达式之前,在功能上分解(将事物分解为做一件事并做得好的较小函数)几乎总是更有效率。
没有浪费大量时间试图理解功能的每个次要方面?
除此之外,功能分解的行为将导致您理解代码的作用,从而导致进一步分解的机会。您希望获得仅包含少量参数的函数,并返回no,一个或几个值。
通过分解,您将更好地理解哪些部分在算法上是必不可少的(必须以这种方式做到正确)以及哪些部分仅仅是实现细节(必须完成,但可以在各种方式完成)不改变输出的不同方式)。
通常情况下,您会发现许多深层嵌套的if
是不必要的,或者是冗余重复的,或者是相似的,如果您分解出微小的差异,它们可以简化为一个或几个函数在其他重复的代码中。
换句话说,您希望获得的句柄不是代码的细节,而是代码处理的抽象事物。许多问题归结为一些数据结构(列表,队列,树,集)和该结构上的一些操作。在某种程度上,您可以分离数据结构和算法,您可以获得简化事物的抽象级别。或者你可以发现,无论你需要做什么都更适合其他结构或算法,然后你可以吹走很多东西。
我记得几年前我的同事一直在编写数据库游标,做任何事情,因为这就是他知道怎么做的事情。设置和拆除游标需要一些样板,以及一个带有错误检查的循环,因此他的代码看起来总是看起来很复杂。他会在存储过程中执行此操作,当然会添加更多样板文件。现在,正如它发生的那样,这是在Sybase T-SQL中,它有一个全局的错误和一个全局的游标状态,两次他都检查两次,一次是他拿到第一个游标行,然后一次在循环中迭代所有其他行。并且他一直将错误变量(@@error
)与游标状态变量(@@sqlstatus
混淆,成功时为零(获得游标行),失败时为1,如果没有更多行则为2在光标中)。他的代码在名义路径上工作,因为如果他已经排成一行,两者都是零,当他试图在最后一行之后获得该行时,他会得到一个错误。因此,如果你仔细查看他的代码,你会再次呻吟(再次!)并为他解决这个问题。
但接下来你会仔细观察,看到他正在通过所有行where x = 1
和每行设置y = y * 2
进行游戏,并且你最终会告诉他,“只需写一个更新声明!”。
你纠正他的全局变量,而正确,没有解决真正的问题,那就是他正在使用光标和存储过程没有充分理由,当他可能有从客户端代码发送了更新声明。
确定输出更难,因为你不仅要查看全局变量的本地使用,而是必须查看两个位置:声明光标的位置(declare cursor_foo cursor for select * from table where x = 1 for update;
)和更新发生位置的二十行(update table set y = y * 2 where current of cursor_foo
)。 (而且所有这些都是以非常模板化的方式进行多线化的。)
弄清楚它也比较困难,因为你只是假设没有人会经历这一切只是为了做更新; 肯定所有这些样板必须是因为更新中发生了一些特殊的事情,对吧?或者因为where
谓词是动态的还是什么?所以你看看这个,并且作为一个尊重他的同事的温和的程序员,你的第一直觉是,“我我在这里缺少什么,必须有理由使用了光标?“
(尽管我和他的老板都在解释@@ error和@@ sqlstatus之间的区别,但他从来没有得到它,更不用说他几乎总能做update
的想法;他认为用的是虽然作为“数据库程序员”具有表面经验,但程序代码并且永远不会“获得”数据库。)
我们的教训是,在你确认首先需要细节(实施细节)之前,不要陷入细节。通过首先分解代码,您可以更好地理解正在处理的抽象,并真正改进代码。
答案 1 :(得分:4)
代码目前是否有足够的单元测试,以便您知道您的更改是否破坏了?如果没有,请在进行任何重构之前先编写它们。没有首先进行单元测试的重构有点像走出街道而不先看。
如果代码有单元测试,您可以依靠它们来理解。如果没有,那么你将不得不很好地理解代码来编写单元测试。这可能并不意味着您需要达到理解细节的水平。但是你必须了解各种不同条件下的期望。您需要知道代码中的名义路径。您需要了解各种替代和错误路径。
老实说,我没有看到你不会对这段代码非常熟悉的情况 - 或者至少是它背后的意图。一旦达到该级别,希望您不仅可以替换代码位,而且可以设计出更好的解决方案。
祝你好运。答案 2 :(得分:3)
在我开始讨论代码之前,我创建了一个单元测试框架,以确保我不会破坏任何内容。然后我开始将所有案例逐个移动到单独的函数中。代码突然变得更具可读性,因为我试图赋予函数可理解的名称,这也迫使我理解代码。然后,一旦完成,我开始查看条件,看看它们是否也可以重新计算,通常可能,因为之前的大量线条可能会掩盖事物。 (我想的是我从某人那里继承的功能,基本上是500行if-then-else)
答案 3 :(得分:0)
弄清楚你的分支是基于“表”还是基于功能。
某些事情(例如,解析器)归结为基本上是一个大的switch语句(对稀疏性进行温和调整),在这种情况下,一个巨大的switch语句可能是最易读的选项。
否则,尝试从功能上思考。一旦你已经分解并分解成函数调用,你就有了构建块来重新安排必要的东西,但你可能会发现它已经不再需要了。这是因为您选择的函数名称应遵循“Do one thing”原则,因此您在重构这些函数时已经在思考。