Closed. This question needs to be more focused. It is not currently accepting answers.
Closed 4 years ago.
Locked. This question and its answers are locked because the question is off-topic but has historical significance. It is not currently accepting new answers or interactions.
--EDIT--
I believe this is a valid question that may have multiple answers (as defined here). This is NOT a discussion nor a poll. Furthermore, this question is evidently NOT argumentative as none of the respondents so far seem to argue with each other.
--ORIGINAL TEXT--
Amazing! I do software for about 15 years now and I still have no idea what I'm doing :)
Seriously, I still struggle with the basics:
overengineering vs. YAGNI
cutting corners in order to meet the deadline vs. pushing back on the business
risky innovations vs. tedious good old stuff
enjoying the freedom of working alone vs. the power of the team
But the worst of all is code complexity. My code tends to get evil. It even bites myself after a few weeks. I put tremendous effort in keeping it simple, readable, maintainable, elegant, beautiful, cohesive, loosely coupled, based on nice straightforward abstractions. But all this effort goes down the drain!
Don't get me wrong, my code is pretty good by most people's standards. I mean it's flexible, more or less unit-testable, and does what it needs to do. However, it is far from simple.
Every change requires substantial refactoring. If another person opened my code with the intent of adding a feature he would HAVE to do something stupid. There's no way he'd be able to do it RIGHT, even if he was a GENIUS, even if he was my own CLONE unfamiliar with the codebase. Why, why for God's sake does this happen? Isn't there some methodology, some technology, some meditation technique, anything at all?!
How to keep my code simple?
As #neodymium points out in a zenly fashion, don't let your algorithms drive the code. To exemplify what that actually means consider the following griefing contraption in C# that you've written a week ago:
public void DoArray(string[] strArr) {
string s = null;
for(int i = 0; i < strArr.Length; i++)
{
if(strArr[i].Equals("Needle"))
s = strArr[i];
}
if(s != null)
Console.WriteLine("I've found " + s);
}
It may take a while to find out what you've written just by looking at your code. But in the end you remember that this method looks for a "Needle" in the string array. You marvel at how ingenious your algorithm is but fret over the fact that it took you a minute or two to realize what it was doing.
Don't start making things clear by commenting!
Your first instinct may be to write comments to help your working fellows out, BUT STOP RIGHT THERE! Comments are nothing but an apology. Instead lets make ourselves clear in code… crystal clear.
In order to do that, try to think as if you've never seen your own code before. Force it by saying things to yourself like: "what is this" and "why is that". And then we'll start to refactor the thing. First simple refactor that we can do is to rename the method into something more fitting. How about:
public void FindNeedle(string[] strArr) { … }
What more can we do? We could:
Rename strArr to something more appropriate, like haystack.
Change the return type to a bool and change the method so it'll return when the needle has been found.
Move the Console.WriteLine(…) part of the method outside the context of this method so the calling code can do it instead like this: if ( FindNeedle(myArray) ) WriteFoundNeedle();
Use foreach instead of for.
The code may end up as following (your milage may vary):
public bool HasNeedle(string[] haystack) {
foreach(string straw in haystack)
{
if(straw.Equals("Needle"))
return true;
}
return false;
}
// Is called by the following:
if ( HasNeedle(strArr) )
{
Console.WriteLine("I've found the needle!");
}
Refactor in small steps
Lots of things can be done in order to make your code more clear, understandable and readable and this is achieved by refactoring your code. Do your refactorings in as small steps as possible. Like moving or encapsulating logic and naming them to something much more logical and readable. Something as simple as a long and convoluted if-statement:
if ( strArr[pos - 1].Equals("do") && strArr[pos + 1].Equals("then") )
… can be refactored into something simpler by moving the logic-statement into its own method:
if ( CurrentIsSurroundedByDoThen(strArr, pos) )
There are lots of ways to refactor. I suggest you read up some of them, Martin Fowler has written a book about it but there is also an on-line catalog available with code samples and/or class diagrams. Choosing what refactoring you should do or not has to be decided under the assumption that it will make your code more maintainable than before.
In addendum
Remember keep your refactorings simple and do it with SOLID principles in mind.
Also if you start out your project by writing unit tests the right way then refactoring becomes something much more natural. The more you refactor, the clearer your code will become.
I feel your pain, buddy. The struggle for simplicity in complex systems is the struggle of software engineering. If you've nailed it, you're probably not working on hard enough engineering problems. And hard doesn't always mean complex, it may be "implement x by tomorrow to keep the sky from falling."
Towards simplicity ... TDD mentioned thoroughly, agree totally. TDD is a trick to keep code focussed on what it needs to do and no more. Re-factor frequently mentioned. Totally agree.
On simplicity vs complexity and working alone ... don't work alone on shipping code. Get code reviews every check in, and encourage code reviewers to rake you over the coals. That will keep you on track to make the right compromises and balances. Talk to someone about your code at least once a day. Rotate reviewers. My work is more lucid and just better with a teammate. Don't care how green they are. Actually, the greener the better to ensure clear code.
On working alone ... Working alone has its place in R&D, not shipped code. At best, lone cowboy projects make cool stuff that is terrible pain to maintain. Work done alone always needs a month or or two to re implement and re-factor into code maintainable by mortals and fix a few huge oversights. It's really really painful if that month or two hits you after you shipped the cowboy code.
Edit: On the detail side, I've found various books on Domain Drive Design extremely helpful in providing ways to create super clear code. DDD not applicable to every problem though.
If you do find the answer to the balance between simplicity and over-engineering ... well, I wouldn't even know what to do then. I suppose I'd get bored and find another line of work.
How do I keep my code simple?
Use code to implement the proper algorithms.
Don't let your algorithms write the code.
For me, Test Driven Development makes all the difference. When I write code without a test justifying it, I think of way too many scenarios, worry if the code will work, write tons of extra stuff to make sure that it will work, etc.
When I do TDD, the code comes out very simple because the tests made me write the right code, I wasn't as defensive, and yet I'm confident that it meets all the requirements because the tests pass.
Another thing which I find helps is to inspect the code of open source projects. Many of them have code which is easy for others to understand and modify, so it gives good insight into how to achieve that. One of my favorites in the that regard is JMock.
One of the simpler ones and my favourite is to introduce explaining variables.
This:
if ( (platform.toUpperCase().indexOf("MAC") > -1) &&
(browser.toUpperCase().indexOf("IE") > -1) &&
wasInitialized() && resize > 0 )
{
// do something
}
Becomes:
final boolean isMacOs = platform.toUpperCase().indexOf("MAC") > -1;
final boolean isIEBrowser = browser.toUpperCase().indexOf("IE") > -1;
final boolean wasResized = resize > 0;
if (isMacOs && isIEBrowser && wasInitialized() && wasResized)
{
// do something
}
Refactor often.
This is the only reliable way I have found to make my code simple. Sometimes you can't think your way out of complexity without starting to work on the code first.
More or less unit testable means that it isn't unit testable. :)
If you can't easily write a test, without jumping through a lot of hoops or setup work, then that would seem to be a problem, IMO, and should require refactoring.
I try to follow three principles:
Make it work
Make it right
Make it fast.
The first is just to get it working, mostly unit testable. :)
Then I refactor and improve the unit tests, complete them. This is the next step you should go to, as it sounds like you may be ignoring this concept. Also, try to remove any hard-coded values to make it more flexible. Put them in xml files, database or as constants in some common file.
Then, I profile and optimize the bottlenecks.
You may want to look at xetreme programming (XP) and understand the concept of implementing only the features that are needed, not trying to implement what you think is needed. It should simplify the code.
I usually code with this quote in mind.
Occam's razor The simplest (explanation|solution) is usually the best one.
I'd suggest you post some of your code and ask for criticism and comments. You can learn a lot from other people. Try it :)
When you are considering if to add or to remove something, ask yourself if it belongs to the 20% of effort that delivers the 80% of the difference.
If it doesn't or you are in doubt, remove it. If what's removed is that important, it will become obvious soon.
Keeping telling yourself "do it simple" also helps. Go for simple and forget about correction, and correction will come and KISS you.
I think that is a common problem. We all try to create elegant code and clean code, and sometimes we end up doing quick and dirty because we are under pressure and time and budgets are not infinite.
I try to follow DRY and KISS. "Don't Repeat Yourself" and "Keep It Simple Stupid". I also follow the "one thing" rule. A method, class, namespace, should only ever do one thing.
To be honest, I've tried doing a project from the test driven development point-of-view, and I found it really hard to get in that mind set, of you Red, Green, Refactor, but it really helped break old habits.
I've been a victim of tight coupling and had to rip things out into their own components, it was brutal and broke the application, but you can get caught up.
This is really a vague question, I agree, community wiki!
p.s. I don't believe something is more or less, it is either is, or it isn't
off the top my head: Build time into your schedule to refactor. Make refactoring part of the requirements to ship, not just an after-thought.
This would be based on wanting to deliver a product that is more maintainable, re-usable, and so future developers on it don't freak out and have to hack.
I really think the answer is making refactoring a bigger part of your life, and being disciplined about it. (I need to this as well! Similar problems...I think most of us are in this boat.)
Only add things that are needed - don't future proof - only react to real problems
You could try running complexity metrics frequently, decide what your upper limit is on complexity, and refactor when you exceed that limit. Complexity metrics are objective. They give you a way of quantifying how complex your software is and a way of measuring progress. Maybe you're beating yourself up over code that's pretty good. Or maybe code you think is simple scores high on a complexity metric.
I know that these metrics are crude. What really matters is the subjective psychological experience of complexity, not the McCabe complexity number, and the latter is a crude approximation to the former. On the other hand, I've found these metrics very useful.
On the KISS theme:
1) Don't feel that you must fix all the tiny little bugs/features in your core code. Sometimes it is better to just leave them documented, and let the guy calling your code worry about how to work around them. (There may be a good chance he will never trigger that particular bug anyway, so any checks would have been a waste of processing.) This keeps your code small, fast and simple. If you want to make a foolproof-but-bloated version, code it separately from (on top of) the library code...
2) Don't let that other guy change your code! He can call your code, or he can extend your code, so if it is already doing its job, does he really need to change the core? Don't let him turn your SimpleAndSpeedySearchPage into a SuperAdvancedSearchPageWithCowbell, make him build the SuperAdvancedSearchPageWithCowbell by extending or calling your code.
If your team-mate does start adding stuff all over your neat little library, do the refactoring yourself. Pull your good code out to a superclass, and leave him just with his code calling yours.
Summary: Once your code is doing its basic job, stop working on it, and make it read-only! If you want to add "advanced features" and "bugfixes specific to your application", add them somewhere else. 100 simple classes are better than 10 bloated classes.
Several things that come to mind:
Try to plan ahead your implementation strategy - possibly on paper or discussing with a peer, before you dive into the implementation. It's likely that there are already a library functions available to handle parts of your problem.
Write down the pseudo code that outlines the steps of your implementation.
Try implementing the methods so that each method has a single purpose, or ties together other methods. It should be possible to see at a glance what any method is supposed to do. This is where TDD can help you to focus on small methods that are easier to test. Smaller methods also have specific names, so the name already tells you a lot. Also, some simple complexity metrics, as the one from this question can help you determine if your methods try to do too much.
Don't stop when your implementation is working - try to refactor until it's as simple an maintainable as possible. Always ask yourself, if someone else would be reading this, would they understand it?
Related
I have a big mess of code. Admittedly, I wrote it myself - a year ago. It's not well commented but it's not very complicated either, so I can understand it -- just not well enough to know where to start as far as refactoring it.
I violated every rule that I have read about over the past year. There are classes with multiple responsibilities, there are indirect accesses (I forget the term - something like foo.bar.doSomething()), and like I said it is not well commented. On top of that, it's the beginnings of a game, so the graphics is coupled with the data, or the places where I tried to decouple graphics and data, I made the data public in order for the graphics to be able to access the data it needs...
It's a huge mess! Where do I start? How would you start on something like this?
My current approach is to take variables and switch them to private and then refactor the pieces that break, but that doesn't seem to be enough. Please suggest other strategies for wading through this mess and turning it into something clean so that I can continue where I left off!
Update two days later: I have been drawing out UML-like diagrams of my classes, and catching some of the "Low Hanging Fruit" along the way. I've even found some bits of code that were the beginnings of new features, but as I'm trying to slim everything down, I've been able to delete those bits and make the project feel cleaner. I'm probably going to refactor as much as possible before rigging my test cases (but only the things that are 100% certain not to impact the functionality, of course!), so that I won't have to refactor test cases as I change functionality. (do you think I'm doing it right or would it, in your opinion, be easier for me to suck it up and write the tests first?)
Please vote for the best answer so that I can mark it fairly! Feel free to add your own answer to the bunch as well, there's still room for you! I'll give it another day or so and then probably mark the highest-voted answer as accepted.
Thanks to everyone who has responded so far!
June 25, 2010: I discovered a blog post which directly answers this question from someone who seems to have a pretty good grasp of programming: (or maybe not, if you read his article :) )
To that end, I do four things when I
need to refactor code:
Determine what the purpose of the code was
Draw UML and action diagrams of the classes involved
Shop around for the right design patterns
Determine clearer names for the current classes and methods
Pick yourself up a copy of Martin Fowler's Refactoring. It has some good advice on ways to break down your refactoring problem. About 75% of the book is little cookbook-style refactoring steps you can do. It also advocates automated unit tests that you can run after each step to prove your code still works.
As for a place to start, I would sit down and draw out a high-level architecture of your program. You don't have to get fancy with detailed UML models, but some basic UML is not a bad idea. You need a big picture idea of how the major pieces fit together so you can visually see where your decoupling is going to happen. Just a page or two of some basic block diagrams will help with the overwhelming feeling you have right now.
Without some sort of high level spec or design, you just risk getting lost again and ending up with another unmaintainable mess.
If you need to start from scratch, remember that you never truly start from scratch. You have some code and the knowledge you gained from your first time. But sometimes it does help to start with a blank project and pull things in as you go, rather than put out fires in a messy code base. Just remember not to completely throw out the old, use it for its good parts and pull them in as you go.
What was most important for me on different occasions were unit tests: I took a few days to write tests for the old code and then I was free to refactor with confidence. How exactly is a different question, but having the tests made it possible for me to make real, substantial changes to the code.
I'll second everyone's recommendations for Fowler's Refactoring, but in your specific case you may want to look at Michael Feathers' Working Effectively with Legacy Code, which is really perfect for your situation.
Feathers talks about Characterization Tests, which are unit tests not to assert known behaviour of the system but to explore and define the existing (unclear) behaviour -- in the case where you've written your own legacy code, and fixing it yourself, this may not be so important, but if your design is sloppy then it's quite possible there are parts of the code that work by 'magic' and their behaviour isn't clear, even to you -- in that case, characterization tests will help.
One great part of the book is the discussion about finding (or creating) seams in your codebase -- seams are natural 'fault lines', if you like, where you can break into the existing system to start testing it, and pulling it towards a better design. Hard to explain but well worth a read.
There's a brief paper where Feathers fleshes out some of the concepts from the book, but it really is well worth hunting down the whole thing. It's one of my favourites.
Just an additional refactoring that is more important than you think: Name things correctly!
This goes for any variable name and method name. If the name does not accurately reflect what the thing is used for, then rename it to something more accurate. This might require several iterations. If you cannot find a short, and entirely accurate name, then that item does too much and you have an excellent candidate for a code snippet that needs to be split. The names also clearly indicate where the cuts are to be made.
Also, document your stuff. Whenever the answer to WHY? is not clearly conveyed by the answer to HOW? (being the code) you will need to add some documentation. Capturing design decisions is probably the most important task as it is very hard to do in code.
You could always start from "scratch". That doesn't mean scrap it and start from nothing, but try to rethink high-level things from the beginning, since you seem to have learned a lot since the last time you worked on it.
Start from a higher level, and as you build the scaffolding of your new and improved structure, take all the code you can reuse, which will probably be more than you think if you're willing to read through it and make some small changes.
When you're making the changes, be sure to be strict with yourself about following all the good practices you now know, because you will really thank yourself later.
It can be surprisingly refreshing to properly re-make program to do exactly what it did before, only more "cleanly". ;)
As others have mentioned as well, unit-tests are your best friend! They help you ensure that your refactoring works, and if you're starting from "scratch", it's the perfect time to write them.
You're in a much better position than many people facing this problem in that you understand what the code is supposed to do.
Taking variables out of a shared scope, as you're doing, is a great start, in that you're partitioning responsibilities. Ultimately you want each class to express a single responsibility. A few other things you might look at:
Easy targets for refactoring are code that's duplicated in lots of places and long methods.
If you're managing application state through statically initialized singletons or worse, a global state that everything is talking to, consider moving it to a managed initialization system (i.e. a dependency injection framework like spring or guice) or at least make sure that the initialization isn't entangled with the rest of the code.
Centralize and standardize how you're accessing outside resources, especially if you've got things like file locations or urls hardcoded.
Buy an IDE that has good refactoring support. I think IntelliJ is the best, but Eclipse has it now, too.
The unit test idea is key as well. You will want to have a suite of large, overall transactions that will give you the overall behavior of the code.
Once you have those, start creating unit tests for classes and smaller packages. Write the tests to demonstrate proper behavior, make your changes, and re-run the tests to demonstrate that you haven't broken everything.
Track code coverage as you go. You'll want to work it up to 70% or better. For the classes you change, you'll want those to be 70% or better before you make your changes.
Build up that safety net over time and you'll be able to refactor with some confidence.
very slowly :D
No seriously... take it one step at a time. For instance, refactor something only if it affects or helps you write the current bug/feature that you are working on right now and do no more than that. And before you refactor make darn sure that you have some kind of automated test in place that gets run on each build that will actually test what you are writing/refactoring. Even if you don't have unit tests, it is never too late to start adding them for all new and modified code that is being written. Over time, your code base will get better in small increments daily or weekly instead of worse - all without you making monumental heaps of changes.
In my personal opinion and experience, it's not worth it to just refactor a (legacy) codebase en masse for the sake of refactoring. In those cases, it's best to just start from scratch and do it right all over again (and very rarely are you afforded the opportunity to do such a thing). Hence, just refactoring incremental is the way to go.
For Java code, my favorite first step is to run Findbugs and then remove all the dead stores, un-used fields, unreachable catch blocks, unused private methods and likely bugs.
Next I run CPD to look for evidence of cut-copy-paste code.
It isn't unusual to be able to reduce the code base by 5% by doing this. It also saves you from refactoring code that is never used.
I think you should use Eclipse as a IDE because it is having many plugins and free of cost.You should now follow the MVC pattern and yes must write test cases using JUnit.Eclipse also have plugin for JUnit and it is providing code refactoring facility too so that will reduce your some work.And always remember that writing a code is not important the main thing is to write clean code.So now give comments everywhere so that not only you but any other person read the code then while reading the code he must feel that he is reading an essay.
Refactor the low-hanging fruit. Nibble away at the easy bits, and as you do that, the harder bits will begin to be a little easier. When there aren't any bits left to refactor, you're done.
The refactorings you'll probably find most useful are Rename Method (and even more trivial Renamings like Field, Variable, and Parameter), Extract Method, and Extract Class. For each refactoring you perform, write the necessary unit tests to make the refactoring safe, and run the entire suite of unit tests after each refactoring. It's tempting - and, let's be honest, pretty safe - to rely on the automated refactorings of your IDE, without the tests - but it's good practice and will be good to have the tests into the future as you add functionality to your project.
You might want to look at Martin Fowler's book Refactoring. This is the book that popularized the term and technique (my thought when taking his course: "I've been doing a lot of this all along, I didn't know it had a name"). A quote from the link:
Refactoring is a controlled technique
for improving the design of an
existing code base. Its essence is
applying a series of small
behavior-preserving transformations,
each of which "too small to be worth
doing". However the cumulative effect
of each of these transformations is
quite significant. By doing them in
small steps you reduce the risk of
introducing errors. You also avoid
having the system broken while you are
carrying out the restructuring - which
allows you to gradually refactor a
system over an extended period of
time.
As others have pointed out, unit tests will allow you to refactor with confidence. And start by reducing code duplication. The book will give you lots of other insights.
Here is a catalog of refactorings.
The correct definition of messy code, is code that hard to maintain and change.
To use more mathematical definition, you can check your code by code metrics tools.
This way, you will keep the code that already good enough, and find very fast, the wrong code.
My experience say, that is very powerful way to improve the quality of your code. (if your tool can show you the result on each build or on realtime)
Throw it away, build it new.
I program in C++. Sometimes there are 1000 ways to do something, and depending on the inspiration/energy, etc of the moment, I can take "the right one" or not, and spend 10 minutes or three days to solve a problem or find a solution or do a task for the boss.
When you are programming, how do you deal with this "open" situations? Use your intuition? Prefer to plan a lot before?
Thanks a lot
from all possible solutions choose one that is easily testable.
implement a test
implement the code.
Repeat until I have the functionality I need to actually solve the problem.
Now check if the code is clean enough. Most of the time it won't. In that case, refactor until it is clean.
Clean enough means: Either considerably cleaner then the average piece of code in the project, or as clean as I am able to write it. Whatever is reached earlier.
Ok I have to admit, this is atleast how I try to write my code.
Choosing the solution by testability as a side effect prefers the well designed solutions.
Try to maximize the amount of work not done.
For an open problem I just produce a minimum code which reflects my currently known information.
This does not mean that I produce low quality code. If I don't care about the quality of the code, I will make my life unnecessary hard for future changes, which will happen, when it is an open problem. If new information is dropping in I try to incorporate them into already existing code. Also the design/architecture will be then aligned to the currently known information.
I would:
Write a unit test that checks off the expected solution to the problem
Implement the first algorithm that comes to mind
Refactor and repeat
This works especially well for a problem you are attacking for the first time. If you are worried your boss will say "stop" after point 2, just do a few iterations before you talk to him/her...
Make it run; make it right; make it simple.
Or to elaborate:
Build the simplest thing that might possibly relate to a solution.
Refine it so it meets requirements. Design and accumulate unit tests; when all the tests run, your project is done.
No, wait! It's not done until the code is so simple that it obviously has no faults. If the code is so complex that it has no obvious faults, you're not being true to your craft.
I usually try to find a well known pattern to apply to a particular problem. If I can't find one, or one close enough to derive my solution, then I do what I believe is best and fully document what I'm doing.
Then, six months later, I refactor it to apply a better pattern someone else has used in a similar situation.
Then, twelve months later, I refactor out the whole goddamn thing and replace it with five lines of code.
Testability is great thing to have.
Another thing I pay lots of attention is readability.
I'm usually going to support that code, so it better be shortest, cleanest way, including minimum of non-task-related entities.
In spirit of DDD, this would mean an almost natural-language description of your task translated into programming language.
Hmmm, i prefer not to answer as plain-text but .Net Code-Segment ;)
I think its easy for C++ Dev to understand the following:
SolutionRefining()
{
result= Requirements.Brianstorming(Current_Experience_Knowledge);
if(!result.Equals("Its Time To Design and Code"))//Assuming analysis is already done!
{
do
{
this.TakeRest_Break_HaveFun();//Multi-Processes OS ;)
result = QuickResearchSession(searchEngine Google, softwareCommunity StackOverflow, inHouseGuru Anonymous);
}
while(!result.Equals("Its Time To Design and Code"));
}
else
{
while(Testing().MajorBugs >= 1 && !Coding().Finished)
Develop().Review_UnitTesting().Commit();//artificial .NET! Don't try it in VS 2010.
SubmitProject().GetMoney().GetLaid();
WakeUp().GoToWork().BeReadyForNewFeaturesAttack();
}
MeanWhile (!Solution.Equals("Standard & Best Practice")) //MeanWhile can be found in .NET Framework 11.7301 Beta RC.45
{
//this method shall always invoked during the developer journey in the Software Life Cycle.
Study("Design Patterns").Understand().UnderstandMore().Apply();
UpdateTheToolKit();
SearchFor("Standard & Best Practice").Consume();
}
finally("Write your own A-Z framework").GetRetired();
}
Long methods are evil on several grounds:
They're hard to understand
They're hard to change
They're hard to reuse
They're hard to test
They have low cohesion
They may have high coupling
They tend to be overly complex
How to convince your fellow developer to write short methods? (weapons are forbidden =)
question from agiledeveloper
Ask them to write unit tests for the methods.
That depends on your definitions of "short" and "long".
When I hear someone say "write short methods", I immediately react badly because I've encountered too much spaghetti written by people who think the ideal method is two lines long: One line to do the tiniest possible unit of work followed by one line to call another method. (You say long methods are evil because "they're hard to understand"? Try walking into a project where every trivial action generates a call stack 50 methods deep and trying to figure out which of those 50 layers is the one you need to change...)
On the other hand, if, by "short", you mean "self-contained and limited to a single conceptual function", then I'm all for it. But remember that this can't be measured simply by lines of code.
And, as tydok pointed out, you catch more flies with honey than vinegar. Try telling them why your way is good instead of why their way is bad. If you can do this without making any overt comparisons or references to them or their practices (unless they specifically ask how your ideas would relate to something they're doing), it'll work even better.
You made a list of drawbacks. Try to make a list of what you'll gain by using short methods. Concrete examples. Then try to convince him again.
I read this quote from somewhere:
Write your code as if the person who has to maintain it is a violent psycho, who knows where you live.
In my experience the best way to convince a peer in these cases is by example. Just find opportunities to show them your code and discuss with them the benefits of short functions vs. long functions. Eventually they'll realize what's better spontaneously, without the need to make them feel "bad" programmers.
Code Reviews!
I suggest you try and get some code reviews going. This way you could have a little workshop on best practices and whatever formatting your company adhers to. This adds the context that short methods is a way to make code more readable and easier to understand and also compliant with the SRP.
If you've tried to explain good design and people just aren't getting it, or are just refusing to get it, then stop trying. It's not worth the effort. All you'll get is a bad rep for yourself. Some people are just hopeless.
Basically what it comes down to is that some programmers just aren't cut out for development. They can understand code that's already written, but they can't create it on their own.
These folks should be steered toward a support role, but they shouldn't be allowed to work on anything new. Support is a good place to see lots of different code, so maybe after a few years they'll come to see the benefits of good design.
I do like the idea of Code Reviews that someone else suggested. These sloppy programmers should not only have their own code reviewed, they should sit in on reviews of good code as well. That will give them a chance to see what good code is. Possibly they've just never seen good code.
To expand upon rvanider's answer, performing the cyclomatic complexity analysis on the code did wonders to get attention to the large method issue; getting people to change was still in the works when I left (too much momentum towards big methods).
The tipping point was when we started linking the cyclomatic complexity to the bug database. A CC of over 20 that wasn't a factory was guaranteed to have several entries in the bug database and oftentimes those bugs had a "bloodline" (fix to Bug A caused Bug B; fix to Bug B caused Bug C; etc). We actually had three CC's over 100 (max of 275) and those methods accounted for 40% of the cases in our bug database -- "you know, maybe that 5000 line function isn't such a good idea..."
It was more evident in the project I led when I started there. The goal was to keep CC as low as possible (97% were under 10) and the end result was a product that I basically stopped supporting because the 20 bugs I had weren't worth fixing.
Bug-free software isn't going to happen because of short methods (and this may be an argument you'll have to address) but the bug fixes are very quick and are often free of side-effects when you are working with short, concise methods.
Though writing unit tests would probably cure them of long methods, your company probably doesn't use unit tests. Rhetoric only goes so far and rarely works on developers who are stuck in their ways; show them numbers about how those methods are creating more work and buggy software.
Finding the right blend between function length and simplicity can be complex. Try to apply a metric such as Cyclomatic Complexity to demonstrate the difficulty in maintaining the code in its present form. Nothing beats a non-personal measurement that is based on testing factors such as branch and decision counts.
Not sure where this great quote comes from, but:
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it"
Force him to read Code Complete by Steve McConnell. Say that every good developer has to read this.
Get him drunk? :-)
The serious point to this answer is the question, "why do I consistently write short functions, and hate myself when I don't?"
The reason is that I have difficulty understanding complex code, be that long functions, things that maintain and manipulate a lot of state, or that sort of thing. I noticed many years ago that there are a fair number of people out there that are significantly better at dealing with this sort of complexity than I am. Ironically enough, it's probably because of that that I tend to be a better programmer than many of them: my own limitations force me to confront and clean up that sort of code.
I'm sorry I can't really provide a real answer here, but perhaps this can provide some insight to help lead us to an answer.
Force them to read the book "Clean Code", there are many others but this one is new, good and an easy read.
Asking them to write Unit tests for the complex code is a good avenue to take. This person needs to see for himself what that debt that complexity brings when performing maintenance or analysis.
The question I always ask my team is: "It's 11 pm and you have to read this code - can you? Do you understand under pressure? Can you, over the phone, no remote login, lead them to the section where they can fix an error?" If the answer is no, the follow up is "Can you isolate some of the complexity?"
If you get an argument in return, it's a lost cause. Throw something then.
I would give them 100 lines of code all under 1 method and then another 100 lines of code divided up between several methods and ask them to write down an explanation of what each does.
Time how long it takes to write both paragraphs and then show them the result.
...Make sure to pick code that will take twice or three times as long to understand if it were all under one method - Main() -
Nothing is better than learning by example.
short or long are terms that can be interpreted differently. For one short is a 2 line method while some else will think that method with no more than 100 lines of code are pretty short.
I think it would be better to state that a single method should not do more than one thing at the same time, meaning it should only have one responsibility.
Maybe you could let your fellow developers read something about how to practice the SOLID principles.
I'd normally show them older projects which have well written methods. I would then step through these methods while explaining the reasons behind why we developed them that way.
Hopefully when looking at the bigger picture, they would understand the reasons behind this.
ps. Also, this exercise could be used in conjuction as a mini knowledge transfer on older projects.
Show him how much easier it is to test short methods. Prove that writing short methods will make it easier and faster for him to write the tests for his methods (he is testing these methods, right?)
Bring it up when you are reviewing his code. "This method is rather long, complicated, and seems to be doing four distinct things. Extract method here, here, and here."
Long methods usually mean that the object model is flawed, i.e. one class has too many responsibilities. Chances are that you don't want just more functions, each one shorter, in the same class, but those responsibilies properly assigned to different classes.
No use teaching a pig to sing. It wastes your time and annoys the pig.
Just outshine someone.
When it comes time to fix a bug in the 5000 line routine, then you'll have a ten-line routine and a 4990-line routine. Do this slowly, and nobody notices a sudden change except that things start working better and slowly the big ball of mud evaporates.
You might want to tell them that he might have a really good memory, but you don't. Some people are able to handle much longer methods than others. If you both have to be able to maintain the code, it can only be done if the methods are smaller.
Only do this if he doesn't have a superiority complex
[edit]
why is this collecting negative scores?
You could start refactoring every single method they wrote into multiple methods, even when they're currently working on them. Assign extra time to your schedule for "refactoring other's methods to make the code maintanable". Do it like you think it should be done, and - here comes the educational part - when they complain, tell them you wouldn't have to refactor the methods if they would have made it right the first time. This way, your boss learns that you have to correct other's lazyness, and your co-workers learn that they should make it different.
That's at least some theory.
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 7 years ago.
Improve this question
When writing a mathematical proof, one goal is to continue compressing the proof. The proof gets more elegant but not necessarily more readable. Compression translates to better understanding, as you weed out unnecessary characters and verbosity.
I often hear developers say you should make your code foot print as small as possible. This can very quickly yield unreadable code. In mathematics, it isn't such an issue since the exercise is purely academic. However, in production code where time is money, having people try to figure out what some very concise code is doing doesn't seem to make much sense. For a little more verbose code, you get readability and savings.
At what point do you stop compressing software code?
I try to reach a level of verbosity where my program statements read like a sentence any programmer could understand. This does mean heavily refactoring my code such that it's all short pieces of a story, so each action would be described in a separate method (an even further level might be to another class).
Meaning I would not reduce my number of characters just because it can be expressed in fewer. That's what code-golf competitions are for.
My rule is say what you mean. One common way I see people go wrong is "strength reduction." Basically, they replace the concept they are thinking with something that seems to skip steps. Unfortunately, they are leaving concepts out of their code, making it harder to read.
For example, changing
for (int i = 0; i < n; i++)
foo[i] = ...
to
int * p = foo, q = foo+n;
while ( *p++ = ... < q );
is an example of a strength reduction that seems to save steps, but it leaves out the fact that foo is an array, making it harder to read.
Another common one is using bool instead of an enum.
enum {
MouseDown,
MouseUp
};
Having this be
bool IsMouseDown;
leaves out the fact that this is a state machine, making the code harder to maintain.
So my rule of thumb would be, in your implementation, don't dig down to a lower level than the concepts you are trying to express.
You can make code smaller by seeing redundancy and eliminating it, or by being clever. Do the former and not the latter.
Here's a good article by Steve McConnell - Best Practices http://www.stevemcconnell.com/ieeesoftware/bp06.htm
I think short/concise are two results from well written code. There are many aspects to make code good and many results from well written code, realize the two are different. You don't plan for a small foot print, you plan for a function that is concise and does a single thing extremely well - this SHOULD lead to a small foot print (but may not). Here's a short list of what I would focus on when writing code:
single focused functions - a function should do only one thing, a simple delivery, multi featured functions are buggy and not easily reusable
loosely coupled - don't reach out from inside one function to global data and don't rely heavily on other functions
precise naming - use meaningful precise variable names, cryptic names are just that
keep the code simple and not complex - don't over use language specific technical wow's, good for impressing others, difficult to easily understand and maintain - if you do add something 'special' comment it so at least people can appreciate it prior to cursing you out
evenly comment - to many comments will be ignored and outdated to few have no meaning
formatting - take pride in how the code looks, properly indented code helps
work with the mind of a code maintenance person - think what it would be like to maintain the code you're writting
do be afraid or to lazy to refactor - nothing is perfect the first time, clean up your own mess
One way to find a balance is to seek for readability and not concise-ness. Programmers are constantly scanning code visually to see what is being done, and so the code should as much as possible flow nicely.
If the programmer is scanning code and hits a section that is hard to understand, or takes some effort to visually parse and understand, it is a bad thing. Using common well understood constructs is important, stay away from the vague and infrequently used unless necessary.
Humans are not compilers. Compilers can eat the stuff and keep moving on. Obscure code is not mentally consumed by humans as quickly as clearly understood code.
At times it is very hard to produce readable code in a complicated algorithm, but for the most part, human readability is what we should look for, and not cleverness. I don't think length of code is really a measure of clearness either, because sometimes a more verbose method is more readable than a concise method, and sometimes a concise method is more readable than a long one.
Also, comments should only supplement, and should not describe your code, your code should describe itself. If you have to comment a line because it isn't obvious what is done, that is bad. It takes longer for most experienced programmers to read an English explanation than it does to read the code itself. I think the book Code Complete hammers this one home.
As far as object names go, the thinking on this has gone through an evolution with the introduction of new programming languages.
If you take the "curly brace" languages, starting with C, brevity was considered the soul of wit. So, you would have a variable to hold a loan value named "lv", for instance. The idea was that you were typing a lot of code, so keep the keystrokes to a minimum.
Then along came the Microsoft-sanctioned "Hungarian notation", where the first letters of a variable name were meant to indicate its underlying type. One might use "fLV", or some such, to indicate that the loan value was represented by a float variable.
With Java, and then C#, the paradigm has become one of clarity. A good name for a loan value variable would be "loanValue". I believe part of the reason for this is the command-completion feature in most modern editors. Since its not necessary to type an entire name anymore, you might as well use as many characters as is needed to be descriptive.
This is a good trend. Code needs to be intelligible. Comments are often added as an afterthought, if at all. They are also not updated as code is updated, so they become out of date. Descriptive, well-chosen, variable names are the first, best and easiest way to let others know what you were coding about.
I had a computer science professor who said "As engineers, we are constantly creating types of things that never existed before. The names that we give them will stick, so we should be careful to name things meaningfully."
There needs to be a balance between short sweet source code and performance. If it is nice source and runs the fastest, then good, but for the sake of nice source it runs like a dog, then bad.
Strive to refactor until the code itself reads well. You'll discover your own mistakes in the process, the code will be easier to grok for the "next guy", and you won't be burdened by maintaining (and later forgetting to change) in comments what you're already expressed in code.
When that fails... sure, leave me a comment.
And don't tell me "what" in the comment (that's what the code is for), tell me "why".
As opposed to long/rambling? Sure!
But it gets to the point where it's so short and so concise that it's hard to understand, then you've gone too far.
Yes. Always.
DRY: Don't Repeat Yourself. That will give you a code that is both concise and secure. Writing the same code several times is a good way to make it hard to maintain.
Now that does not mean you should make a function of any blocks of code looking remotely alike.
A very common error (horror ?) for instance is factorizing code doing nearly the same thing, and to handle the differences between occurences by adding a flag to function API. This may look inocuous at first, but generates code flow hard to understand and bug prone, and even harder to refactor.
If you follow common refactoring rules (looking about code smells) your code will become more and more concise as a side effect as many code smells are about detecting redundancy.
On the other hand, if you try to make the code as short as possible not following any meaningfull guidelines, at some point you will have to stop because you just won't see any more how to reduce code.
Just imagine if the first step is removing all useless whitespaces... after that step code in most programming languages will become so hard to read you won't have much chance to find any other possible enhancement.
The example above is quite caricatural, but not so far from what you get when trying to optimise for size without following any sensible guideline.
There's no exact line that can be drawn to distinguish between code that is glib and code that is flowery. Use your best judgment. Have others look at your code and see how easily they can understand it. But remember, correctness is the number 1 goal.
The need for small code footprints is a throwback from the days of assembly language and the first slightly high level languages... there small code footprints where a real and pressing need. These days though, its not so much of a necessity.
That said, I hate verbose code. Where I work, we write code that reads as much as possible like a natural language, without any extra grammar or words. And we don't abbreviate anything unless its a very common abbreviation.
Company.get_by_name("ABC")
makeHeaderTable()
is about as terse as we go.
In general, I make things obvious and easy to work with. If concision/shortness serves me in that end, all the better. Often short answers are the clearest, so shortness is a byproduct of obvious.
There are a couple points to my mind that determine when to stop optimizing:
Worth of spending time performing optimizations. If you have people spending weeks and not finding anything, are there better uses of those resources?
What is the order of optimization priority. There are a few different factors that one could care about when it comes to code: Execution time, execution space(both running and just the compiled code), scalability, stability, how many features are implemented, etc. Part of this is the trade off of time and space, but it can also be where does some code go, e.g. can middleware execute ad hoc SQL commands or should those be routed through stored procedures to improve performance?
I think the main point is that there is a moderation that most good solutions will have.
The code optimizations have little to do with the coding style. The fact that the file contains x spaces or new lines less than at the beginning does not make it better or faster, at least at the execution stage - you format the code with white characters that are unsually ignored by the compiler. It even makes the code worse, because it becomes unreadable for the other programmers and yourself.
It is much more important for the code to be short and clean in its logical structure, such as testing conditions, control flow, assumptions, error handling or the overall programming interface. Of course, I would also include here smart and useful comments + the documentation.
There is not necessarily a correlation between concise code and performance. This is a myth. In mature languages like C/C++ the compilers are capable of optimizing the code very effectively. There is cause need in such languages to assume that the more concise code is the better performing code. Newer, less performance-optimized languages like Ruby lack the compiler optimization features of C/C++ compilers, but there is still little reason to believe that concise code is better performing. The reality is that we never know how well code will perform in production until it gets into production and is profiled. Simple, innocuous, functions can be huge performance bottlenecks if called from enough locations within the code. In highly concurrent systems the biggest bottlenecks are generally caused by poor concurrency algorithms or excessive locking. These issues are rarely solved by writing "concise" code.
The bottom line is this: Code that performs poorly can always be refactored once profiling determines it is the bottleneck. Code can only be effectively refactored if it is easy to understand. Code that is written to be "concise" or "clever" is often more difficult to refactor and maintain.
Write your code for human readability then refactor for performance when necessary.
My two cents...
Code should be short, concrete, and concentrated. You can always explain your ideas with many words in the comments.
You can make your code as short or compact as you like as long as you comment it. This way your code can be optimized but still make sence. I tend to stay in the middle somewhere with descriptive variables and methods and sparce comments if it is still unclear.
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question does not appear to be about programming within the scope defined in the help center.
Closed 9 years ago.
Improve this question
Question
My question is how can you teach the methods and importance of tidying-up and refactoring code?
Background
I was recently working on a code review for a colleague. They had made some modifications to a long-gone colleagues work. During the new changes, my colleague had tried to refactor items but gave up as soon as they hit a crash or some other problem (rather than chasing the rabbit down the hole to find the root of the issue) and so reimplemented the problem code and built more on top of that. This left the code in a tangle of workarounds and magic numbers, so I sat down with them to go through refactoring it.
I tried to explain how I was identifying the places we could refactor and how each refactoring can often highlight new areas. For example, there were two variables that stored the same information - why? I guessed it was a workaround for a bigger issue so I took out one variable and chased the rabbit down the hole, discovering other problems as we went. This eventually led to finding a problem where we were looping over the same things several times. This was due in no small part to the use of arrays of magic number sizes that obfuscated what was being done - fixing the initial "double-variable" problem led to this discovery (and others).
As I went on this refactoring journey with my colleague, it was evident that she wasn't always able to grasp why we made certain changes and how we could be sure the new functionality matched the original, so I took the time to explain and prove each change by comparing with earlier versions and stepping through the changes on paper. I also explained, through examples, how to tell if a refactoring choice was a bad idea, when to choose comments instead of code changes, and how to select good variable names.
I felt that the process of sitting together to do this was worthwhile for both myself (I got to learn a bit more about how best to explain things to others) and my colleague (they got to understand more of our code and our coding practices) but, the experience led me to wonder if there was a better way to teach the refactoring process.
...and finally...
I understand that what does or does not need refactoring, and how to refactor it are very subjective so I want to steer clear of that discussion, but I am interested to learn how others would tackle the challenge of teaching this important skill, and if others here have had similar experiences and what they learned from them (either as the teacher or the student).
Like most programming, refactoring skill comes with practice and experience. It would be nice to think it can be taught, but it has to be learned - and there is a significant difference in the amount of learning that can be accomplished in different environments.
To answer your question, you can teach refactoring methods and good design in a pedagogical fashion, and that's fine. But, ultimately, you and I both know attaining a certain level is only through long hard experience.
I am not 100% to understand your question but I think you can refer yourself to Code Smell that need to be refactored.It contain a lot of example that you could show to other.
Here is a list of when refactoring should be used (list of code smell)
If you haven't read it, Martin Fowler has an excellent book on the subject called Refactoring: Improving the Design of Existing Code. He goes into substantial detail about how and why a specific piece of code should be refactored.
I hesitated to even mention it for fear that knowledge of this book is assumed by someone asking about refactoring, and that you would think, "Duh, I meant besides the Fowler book." But what the hey, there you go. :-)
You don't mention tests. To 'prove' that a refactoring does not break the existing functionality you need to either have existing tests or write tests before doing the refactoring.
Pair Programming seems to be the best way for me to get this across. This way, as we're working on real, production code, and we both encounter some code that doesn't smell right, we tackle a code refactoring together. The pair acts as the driver's conscience saying to do the right thing instead of the quick fix, and in turn, they both learn what good code looks like in the process.
Refactoring can be an art, and just takes practice. The more you do it, the better you get at it. Keep studying the methods described in Martin Fowler's Ractoring book, and use your tools (Resharper for Visual Studio folk)
One simple way to conceive of refactoring is right there in the name -- it's just like when you factor a common variable out of an equation:
xy + xz
becomes
x(y + z)
The x has been factored out. Refactoring code is the same thing, in that you're finding duplicate code or logic and factoring it out.
It sounds like your approach is a very good one. At the end of the process, you showed how you were able to uncover and fix a lot of problems. For educational purposes, it could then be interesting to invent a new change/enhancement/fix. You could then ask your mentoree how they would enact that change with the old a new codebase. Hopefully they'll see that it's much easier to make the new change with the refactored code (or how doing more refactoring would be the easiest way to prepare for the hypothetical change).
I see a couple of different ways you could try to teach refactoring:
Given textbook-like examples. A downside here is that you may have contrived or simplistic examples where why refactoring is useful doesn't necessarily shine through as well as in other cases.
Refactoring existing code. In this case you could take existing legacy code that you'd clean up, or your own code in development and show the before and after doing various bits to see how much better the after is, in terms of readability and ease of maintanence. This may be a better exercise to do as this is live code being improved and enhanced to some extent.
It isn't something that someone can pick up instantly, it takes time, practice, effort and patience as some refactorings may be done for personal preference rather than because the code runs optimally one way or another.
Teaching someone to refactor when they aren't a natural is a tough job. In my experience your best bet is to sit down with them in an office and refactor some code. While you are doing this keep up a "stream of consciousness" dialog. Talk about what you see, why the code doesn't smell right, options to refactor to, etc. Also you should make sure they're doing the same thing. The most important thing is to impart why, not how, to change the code. Any decent programmer can make a change and have it work, but it takes skill and experience to be able to state why the new solution is better than the previous.