Refactoring isn't something I set aside time to do separately. I'm constantly doing it as I develop new code, or when I'm maintaining old code. Work it in to your normal routine and always look for things you can improve in your code.
To respond to the specific case you added in your own answer to this question: I think your situation is a perfect example of when it's a good idea to refactor instead of a total rewrite. Make sure you write a good set of test cases for the code in question before you change a line of code. If the code fails some tests it will serve three purposes. First, it will give you specific areas to focus your efforts on. Second it will give you a solid justification (to your boss) for working on the code. Third is the standard unit test safety net that you want to have in place any time you refactor code.
The standard TDD way is Red-Green-Refactor: make a test that fails, write the code to pass the test, then refactor existing code while still passing tests. Refactoring occurs after tests pass and when you find code that is too complicated or uses bad patterns. Refactoring should be part of your normal, daily development process and not an add-on at the end of a development cycle. It works better, I think, to keep the refactorings small. Doing it as part of your regular activity keeps the poor code from growing too large before the refactoring takes place -- at least ideally.
I tend to see either 'code smells' like I'm repeating the same code over and over again or I see something that makes me think, "There has to be a better way to do this and I will go find it." It is part of how I write code and think it is somewhat of a good thing to have good code that may take a little longer to complete but that is much more easily scalable, maintainable, or have someone else take it and not have to spend days figuring out what I was doing in the code.
If you're inheriting code, then I tend to think there are 2 schools of thought on what to do with it:
1) Keep your distance. This is where you make the required changes to get the feature in and don't do anymore. If you know the module will be replaced soon or you only work on this once or twice a year, then I can see the logic in not wanting to spend lots of time fixing it.
2) Immerse yourself and fix it now. If what you doing is fairly extensive changes or is a piece of code that you'll be working with regularly, then it may be viewed as part of the maintenance to do some refactoring or documentation or however you'd want to describe where bad code is turned into not so bad code as this will save you time later on.
Refactoring is something I do continuously through development, not something I plan for. Whenever the code suggest it could be better structured in some way, I make the appropriate changes.
You can never expect to get the design exactly right. The actual nuances reveal themselves during implementation and by constantly refactoring you always strive to reach a better designed and factored code.
I think the correct answer is: always! While working on new feature if I see a piece of code that I can refactor I just do it. Because I use TDD I don't fear that old functionality will stop working.
I usually don't do it unless I know I'm going to be duplicating code. So, when writing a new feature, if I find myself saying "hmm I did something like this somewhere else..." I try to see how I can refactor the original to enable as much code reuse as possible.
I refactor as I go and I try to keep it quick and safe - the better that area of the code is tested, the more I can do quickly and safely.
In addition, I mark areas or architectural issues I think need a bigger overhaul, and try to schedule those larger sessions separately - usually, what makes them larger is lack of tests, which means I have to spend a while adding those tests that I need.
To get into a specific problem: There's a project where some bad code was written (even by people who are no longer in the company) during a few months. A full rewrite would be unfeasible and I couldn't explain it to either the client or management.
So I was wondering if refactoring a certain module before doing changes on that module would be acceptable in that situation.
I know it's not the best scenario but the context is a special case (code already broken, can't rewrite it all).
I will say that I look for code smells as well, but I would like to be more specific. I am using a framework of my design that is growing and evolving with each project. I will tend to heavily refactor and redesign (disciplining myself to keep these separate is something I'm still working on) early in a project and as I get closer to a deadline and closer to solving any issues or code smells I will ease off and focus on my specific implementation. As I do this, I will usually find (or create) a few more things that, while functional, I am not entirely happy with. I will log these and on my next iteration through the project I will address these issues.
When I come back to the code, I will sometimes find that there is a more elegant way to handle the situation and kick myself for not seeing it sooner. Sometimes, I will find there is a better way, but it is not the way I had originally envisioned. Sometimes I find that it is good the way it is and that changing it would be over-designing. Other times I find that in fixing something else, my original issue has disappeared.
When I prepare to make a functional change to the code (feature, bug fix, performance, whatever), I first ask myself what it would look like if the code was ideally-structured to make that change.
Then I refactor in that direction
I now make the functional change.
Finally, I refactor again to clean up the ugliness introduced by my change.
I always want to balance my refactoring work against other factors (deadlines, importance of this code, quality of test coverage, etc.).
If you don't need to change that code (it works, no need for extend it), don't do it. Let it be. In other way, if it needs changes in it, write some test a refactor it and include your changes.
I consider myself a novice programmer (I've been doing programming for a living only for 6 months at the moment) and I've noticed that just by looking at the code you should get the feeling if it needs refactoring or not.
As far as I understand some refer to this as "code smell" but I'd say it's more of a unjust feeling that you haven't done your best with code you're eyeing at. You may not be sure what to do or how to improve the code but if you have even the littlest doubt of that the code isn't perfect then it most likely isn't.
You can often make the scope even smaller than a module. Sometimes a single function will be an obvious candidate for refactoring in isolation, even if it's just renaming local variables, removing the need for comments by making the code explain itself, stuff like that.
If you can identify an area that needs to be changed, clean up in that area before and during the change being made. Often I find I need to perform some refactoring to get a enough understanding of the code to make the change at all.
I'd add my voice to everyone else who says to get some sort of testing wrapped around the code, though. Try to at least cover a reasonable set of "normal" cases and get some automation in place (there are frameworks available for just about every language) so that it's easy and fast to run your tests after every small change. I wish I'd thought of/known about testing frameworks when I first started my code cleaning activities...
If I keep visiting the same code over and over again when fixing bugs, I figure it's time to refactor it. If I've joined a new project or become responsible for new code, I also sit down and start refactoring. If I am extending something, then prior to any big changes I refactor out all of the old problems first (before they become too ingrained).
You've finished refactoring when you've reached some targeted level of normalization. If it's just general clean up: 1,2 or 3 is good enough. If you're extending the code, then 4 or 5 are better. If you're really trying to leverage the existing work over a long period, then 6 is the way to go.
To answer only part of your question: I'd reiterate a point made by some of the others here already - in general, if you don't have a repeatable set of tests that you can run to make sure that what you changed hasn't broken the code - then you probably shouldn't refactor at all.
You said the code is already broken. You might be tempted to go with initially making as few, small changes to it as possible, in order to get it "working". Trouble is, without a test, how can you say it really is "working"?
发布评论
评论(16)
重构不是我专门花时间去做的事情。 当我开发新代码或维护旧代码时,我会不断地这样做。 将其纳入您的日常工作中,并始终寻找可以在代码中改进的地方。
为了回应您在这个问题的答案中添加的具体案例:我认为您的情况是一个完美的例子,说明何时重构而不是完全重写是个好主意。 在更改一行代码之前,请确保为相关代码编写一组良好的测试用例。 如果代码未通过某些测试,它将达到三个目的。 首先,它将为您提供重点关注的具体领域。 其次,它会给你(对你的老板)处理代码的坚实理由。 第三是标准单元测试安全网,您希望在重构代码时随时使用它。
Refactoring isn't something I set aside time to do separately. I'm constantly doing it as I develop new code, or when I'm maintaining old code. Work it in to your normal routine and always look for things you can improve in your code.
To respond to the specific case you added in your own answer to this question: I think your situation is a perfect example of when it's a good idea to refactor instead of a total rewrite. Make sure you write a good set of test cases for the code in question before you change a line of code. If the code fails some tests it will serve three purposes. First, it will give you specific areas to focus your efforts on. Second it will give you a solid justification (to your boss) for working on the code. Third is the standard unit test safety net that you want to have in place any time you refactor code.
标准的 TDD 方式是红绿重构:进行失败的测试,编写代码以通过测试,然后重构现有代码,同时仍然通过测试。 重构发生在测试通过之后,以及当您发现代码过于复杂或使用了错误的模式时。 重构应该是正常的日常开发过程的一部分,而不是开发周期结束时的附加组件。 我认为,保持较小的重构效果更好。 将其作为常规活动的一部分来进行,可以防止不良代码在重构发生之前变得太大——至少在理想情况下是这样。
The standard TDD way is Red-Green-Refactor: make a test that fails, write the code to pass the test, then refactor existing code while still passing tests. Refactoring occurs after tests pass and when you find code that is too complicated or uses bad patterns. Refactoring should be part of your normal, daily development process and not an add-on at the end of a development cycle. It works better, I think, to keep the refactorings small. Doing it as part of your regular activity keeps the poor code from growing too large before the refactoring takes place -- at least ideally.
我倾向于看到“代码味道”,就像我一遍又一遍地重复相同的代码,或者我看到一些让我思考的东西,“必须有更好的方法来做到这一点,我会去找它。” 这是我编写代码的方式的一部分,我认为拥有好的代码可能是一件好事,虽然可能需要更长的时间才能完成,但更容易扩展、维护,或者让其他人使用它而不必花钱花了几天时间弄清楚我在代码中做了什么。
如果您要继承代码,那么我倾向于认为关于如何处理它有两种思想流派:
1)保持距离。 您可以在此处进行所需的更改以获取该功能,但无需再执行此操作。 如果您知道该模块很快就会被更换,或者您每年只处理一次或两次,那么我可以理解不想花费大量时间来修复它的逻辑。
2)让自己沉浸其中并立即修复它。 如果您所做的是相当广泛的更改,或者是您将定期使用的一段代码,那么它可能会被视为进行一些重构或文档的维护的一部分,或者您想要描述错误代码的位置变成了不太糟糕的代码,因为这将节省您以后的时间。
I tend to see either 'code smells' like I'm repeating the same code over and over again or I see something that makes me think, "There has to be a better way to do this and I will go find it." It is part of how I write code and think it is somewhat of a good thing to have good code that may take a little longer to complete but that is much more easily scalable, maintainable, or have someone else take it and not have to spend days figuring out what I was doing in the code.
If you're inheriting code, then I tend to think there are 2 schools of thought on what to do with it:
1) Keep your distance. This is where you make the required changes to get the feature in and don't do anymore. If you know the module will be replaced soon or you only work on this once or twice a year, then I can see the logic in not wanting to spend lots of time fixing it.
2) Immerse yourself and fix it now. If what you doing is fairly extensive changes or is a piece of code that you'll be working with regularly, then it may be viewed as part of the maintenance to do some refactoring or documentation or however you'd want to describe where bad code is turned into not so bad code as this will save you time later on.
重构是我在开发过程中不断进行的事情,而不是我计划好的事情。 每当代码表明可以以某种方式更好地构建它时,我都会进行适当的更改。
你永远不能期望设计完全正确。 实际的细微差别会在实现过程中显现出来,并且通过不断重构,您始终会努力获得更好的设计和分解的代码。
Refactoring is something I do continuously through development, not something I plan for. Whenever the code suggest it could be better structured in some way, I make the appropriate changes.
You can never expect to get the design exactly right. The actual nuances reveal themselves during implementation and by constantly refactoring you always strive to reach a better designed and factored code.
我认为正确的答案是:总是!
在开发新功能时,如果我看到一段可以重构的代码,我就会这么做。
因为我使用 TDD,所以我不担心旧功能会停止工作。
I think the correct answer is: always!
While working on new feature if I see a piece of code that I can refactor I just do it.
Because I use TDD I don't fear that old functionality will stop working.
当您的代码 气味
When your code smells
我通常不会这样做,除非我知道我会重复代码。 因此,在编写新功能时,如果我发现自己说“嗯,我在其他地方做了类似的事情......”我会尝试看看如何重构原始功能以实现尽可能多的代码重用。
I usually don't do it unless I know I'm going to be duplicating code. So, when writing a new feature, if I find myself saying "hmm I did something like this somewhere else..." I try to see how I can refactor the original to enable as much code reuse as possible.
我一边重构,一边努力保持快速和安全——代码区域测试得越好,我就能快速、安全地做更多的事情。
此外,我标记了我认为需要进行更大检修的区域或架构问题,并尝试单独安排这些较大的会话 - 通常,导致它们变大的原因是缺乏测试,这意味着我必须花一些时间添加我需要的测试。
I refactor as I go and I try to keep it quick and safe - the better that area of the code is tested, the more I can do quickly and safely.
In addition, I mark areas or architectural issues I think need a bigger overhaul, and try to schedule those larger sessions separately - usually, what makes them larger is lack of tests, which means I have to spend a while adding those tests that I need.
讨论一个具体问题:有一个项目在几个月内编写了一些糟糕的代码(甚至是由不再在公司工作的人编写的)。 完全重写是不可行的,我无法向客户或管理层解释。
所以我想知道在这种情况下,在对某个模块进行更改之前重构该模块是否可以接受。
我知道这不是最好的场景,但上下文是一个特殊情况(代码已经损坏,无法全部重写)。
To get into a specific problem: There's a project where some bad code was written (even by people who are no longer in the company) during a few months. A full rewrite would be unfeasible and I couldn't explain it to either the client or management.
So I was wondering if refactoring a certain module before doing changes on that module would be acceptable in that situation.
I know it's not the best scenario but the context is a special case (code already broken, can't rewrite it all).
我会说我也会寻找代码气味,但我想更具体一些。 我使用的设计框架随着每个项目的发展而不断发展。 我会倾向于在项目的早期进行大量的重构和重新设计(约束自己将这些分开是我仍在努力的事情),当我接近截止日期并接近解决任何问题或代码异味时,我会放松下来并重点看我的具体实现。 当我这样做时,我通常会发现(或创建)更多一些东西,虽然它们很实用,但我并不完全满意。 我将记录这些问题,并在项目的下一次迭代中解决这些问题。
当我回到代码时,有时我会发现有一种更优雅的方法来处理这种情况,并因为没有早点看到它而自责。 有时,我会发现有更好的方法,但它不是我最初设想的方法。 有时我发现它本来的样子就很好,改变它就会过度设计。 有时我发现在解决其他问题时,我原来的问题已经消失了。
I will say that I look for code smells as well, but I would like to be more specific. I am using a framework of my design that is growing and evolving with each project. I will tend to heavily refactor and redesign (disciplining myself to keep these separate is something I'm still working on) early in a project and as I get closer to a deadline and closer to solving any issues or code smells I will ease off and focus on my specific implementation. As I do this, I will usually find (or create) a few more things that, while functional, I am not entirely happy with. I will log these and on my next iteration through the project I will address these issues.
When I come back to the code, I will sometimes find that there is a more elegant way to handle the situation and kick myself for not seeing it sooner. Sometimes, I will find there is a better way, but it is not the way I had originally envisioned. Sometimes I find that it is good the way it is and that changing it would be over-designing. Other times I find that in fixing something else, my original issue has disappeared.
当我准备对代码进行功能更改(功能、错误修复、性能等)时,我首先会问自己,如果代码的结构非常理想,可以进行更改,那么会是什么样子。
然后我朝那个方向重构
我现在进行功能更改。
最后,我再次重构以清理由我的更改带来的丑陋。
我总是想平衡我的重构工作和其他因素(截止日期、代码的重要性、测试覆盖率的质量等)。
When I prepare to make a functional change to the code (feature, bug fix, performance, whatever), I first ask myself what it would look like if the code was ideally-structured to make that change.
Then I refactor in that direction
I now make the functional change.
Finally, I refactor again to clean up the ugliness introduced by my change.
I always want to balance my refactoring work against other factors (deadlines, importance of this code, quality of test coverage, etc.).
如果您不需要更改该代码(它可以工作,不需要扩展它),请不要这样做。 随它去。 换句话说,如果需要更改,请编写一些测试并重构它并包含您的更改。
If you don't need to change that code (it works, no need for extend it), don't do it. Let it be. In other way, if it needs changes in it, write some test a refactor it and include your changes.
我认为自己是一个新手程序员(我目前只以编程为生 6 个月),并且我注意到,只需查看代码,您就应该感觉到它是否需要重构。
据我了解,有些人将此称为“代码味道”,但我想说,这更多的是一种不公正的感觉,即您没有尽最大努力处理您所关注的代码。 您可能不确定要做什么或如何改进代码,但如果您对代码不完美有哪怕一点点怀疑,那么它很可能并不完美。
I consider myself a novice programmer (I've been doing programming for a living only for 6 months at the moment) and I've noticed that just by looking at the code you should get the feeling if it needs refactoring or not.
As far as I understand some refer to this as "code smell" but I'd say it's more of a unjust feeling that you haven't done your best with code you're eyeing at. You may not be sure what to do or how to improve the code but if you have even the littlest doubt of that the code isn't perfect then it most likely isn't.
您通常可以使范围比模块更小。 有时,单个函数显然是单独重构的候选者,即使它只是重命名局部变量,通过使代码自我解释来消除注释的需要,诸如此类。
如果您可以确定需要更改的区域,请在更改之前和更改期间清理该区域。 我经常发现我需要执行一些重构才能对代码有足够的了解才能进行更改。
不过,我会向其他所有说要对代码进行某种测试的人表示赞同。 尝试至少涵盖一组合理的“正常”情况,并实现一些自动化(几乎每种语言都有可用的框架),以便在每次小的更改后都可以轻松快速地运行测试。 我希望当我第一次开始代码清理活动时就想到/了解测试框架......
You can often make the scope even smaller than a module. Sometimes a single function will be an obvious candidate for refactoring in isolation, even if it's just renaming local variables, removing the need for comments by making the code explain itself, stuff like that.
If you can identify an area that needs to be changed, clean up in that area before and during the change being made. Often I find I need to perform some refactoring to get a enough understanding of the code to make the change at all.
I'd add my voice to everyone else who says to get some sort of testing wrapped around the code, though. Try to at least cover a reasonable set of "normal" cases and get some automation in place (there are frameworks available for just about every language) so that it's easy and fast to run your tests after every small change. I wish I'd thought of/known about testing frameworks when I first started my code cleaning activities...
如果我在修复错误时不断地一遍又一遍地访问相同的代码,我认为是时候重构它了。 如果我加入了一个新项目或负责新代码,我也会坐下来开始重构。 如果我要扩展某些东西,那么在进行任何重大更改之前,我会首先重构所有旧问题(在它们变得过于根深蒂固之前)。
当您达到规范化<的某个目标级别时,您就完成了重构/a>. 如果只是一般清理:1,2 或 3 就足够了。 如果您要扩展代码,则 4 或 5 更好。 如果您真的想长期利用现有的工作,那么 6 就是最佳选择。
保罗.
If I keep visiting the same code over and over again when fixing bugs, I figure it's time to refactor it. If I've joined a new project or become responsible for new code, I also sit down and start refactoring. If I am extending something, then prior to any big changes I refactor out all of the old problems first (before they become too ingrained).
You've finished refactoring when you've reached some targeted level of normalization. If it's just general clean up: 1,2 or 3 is good enough. If you're extending the code, then 4 or 5 are better. If you're really trying to leverage the existing work over a long period, then 6 is the way to go.
Paul.
仅回答您问题的一部分:我想重申这里其他一些人已经提出的观点 - 一般来说,如果您没有一组可重复的测试,您可以运行这些测试来确保您所做的更改没有改变没有破坏代码 - 那么你可能根本不应该重构。
你说代码已经被破坏了。 您可能会倾向于一开始就对其进行尽可能少的小更改,以便使其“正常工作”。 问题是,没有经过测试,你怎么能说它真的“有效”呢?
祝你好运!
To answer only part of your question: I'd reiterate a point made by some of the others here already - in general, if you don't have a repeatable set of tests that you can run to make sure that what you changed hasn't broken the code - then you probably shouldn't refactor at all.
You said the code is already broken. You might be tempted to go with initially making as few, small changes to it as possible, in order to get it "working". Trouble is, without a test, how can you say it really is "working"?
Good luck to you!