In Outliving the Great Variable Shortage, Tim Ottinger invokes Curly's Law:
This is a companion discussion topic for the original blog entry at: http://www.codinghorror.com/blog/2007/03/curlys-law-do-one-thing.html
In Outliving the Great Variable Shortage, Tim Ottinger invokes Curly's Law:
So what exactly do you propose to your little example with 3 options
See Uncle Bobâs solution here-- basically, multiple classes. In .NET, partial classes.
http://butunclebob.com/ArticleS.UncleBob.SrpInRuby
The goal is to make localized changes in one area of the codeâavoiding âshotgun surgeryâ changes to multiple areas.
Iâm not familiar with Ruby, but splitting the employee class into 3 partial classes doesnât seem like the right solution to me. I would much rather use IoC (Inversion of control) and define an interface for Save (ISave), calculate (ICalculatePay) and report (IReportHours) respectively and have their implementations injected into the Employee class. That way when any of the implementations change the change will be done in the respective class and not in the Employee class. The employee is totally unaware of how these actions are carried out. Another advantage would be that these classes might be used by other parts of the system since they are not necessarily specific to the Employee class.
OK, once again: the RDMBS mantra â one fact, one place, one time. From that, one generates the interface code. Change the schema, regenerate the interface code, start the build, go home to the wife and kids. What more do you want?
Oh, yeah. A framework that actually does that. A few exist, some java, some not.
LOL. Maybe the Embedded Linux community should adopt this paradigm. In the Atmel world, they initialize board peripherals 3 times. Once in the data flash boot loader, once in the u-boot boot loader, and once in Linux. LOL. I laugh because itâs true.
On the other hand, I have a customer whoâs RS-485 doesnât work because those pins are initialized in 3 different places, and they only need to be written to once after reset. The issue isnât which place should initialize them, but how to set it up so itâs fast and doesnât DRY and is easy to maintain going forward.
Iâve definitely been bitten by DRY more than once:
-Still doesnât work? But I did fix it! Look when you do it in X it works. What, They do it from Y? Oh gee i forgot about thatâŚ
-Iâd swear iâve already done that before.
Even though I know duplicating is bad I still do it without even realizing!
Examples :
-I perform some operation in the application when the user changes data (for example computing some kind of sum of that data). And I do the same operation (the sum) when I fetch data from the database.
-Performing an operation on different data : DoXToA(), DoXToB(), DoXToC() failing to realize that it really is the same operation for all three and could do like this instead : DoX(A/B/C)
-Three databases (development/test/production) and modifications are done manually. Everytime something is done we musnât forget that I have/will have to reflect the changes on the others databases.
Sometimes itâs easy to find duplication in some code⌠but not before you actually code it.
Hilarious, how many ways can we say âDonât repeat yourselfâ?
When explaining these concepts to junior developers or to non-tech staff I make an analogy to noting down a decision. Once we have come to a decision about how we want something to be we write it down in one spot. If new information causes us to change that decision we have one place we can update that decision. Our code and data should represent all the decision-making about how our tool or product should work. When we change our minds about a particular decision we shouldnât need to be changing it in multiple places. Oh⌠I am starting to repeat myselfâŚ
Ah yes, the âalmost-eternalâ question, âWhat do I break if I change this here?â
Does that mean you have to not re-use i for loops?
I used to program in a text editor. I liked it better than all those pop-up windows.
I used header files extensively, it helped make the code more self-documented.
Also I prefixed all my variables with lowercase type reminders.
aVar for array, nVar, cVar, sVar, bVar, etc.
âDoes that mean you have to not re-use i for loops? :)â
No. It means that you should only use i for loops!
So what exactly do you propose to your little example with 3 options??? Even if you make the Employee essentially a container and have multiple classes handle Employee actions, or more likely system generic actions, you may have single point of failure per class, put you maybe have multiple classes that are affected! How is that for a long sentence!
Yes, we all know what code should look like. Unfortunately everybody have their own ideals and our ideals also change over time. At least mine do :-). But the big question is: what do we do when we are faced with reality (i.e. deadlines)? I often have to comprise to quick nâ dirty because I simply do not have the time to refactor properly. :-/
I really donât see a whole lot of advantage of scattering the code across multiple files. This would seem to lend itself to a file-per-method result, no?
The sin would be to insert business logic into the Employee class itself (not that I think this example is necessarily presenting that).
My fear, especially given the number of times Iâve heard (wrongly) that partial classes are the solution to separating business logic from view/model, is that this would lead people to think that some partial class of calculatePay() justifies embedding the business logic in there (and then the Contractor class comes in andâŚ)
DRY is definitely something to strive for. Agile development practices (such as those Iâve seen at length in books by Jeffries and the Pragmatic team) certainly aim for DRY, but not at the expense of clarity. As Jeff A. notes at the end of this article, we frequently canât solve the problem until we have enough solvable problems, which is why Agile requires a blend of quick, make it work and pass a test code with an aggressive round of no-holds-barred refactoring. Read Jeffriesâ Adventures in C# book for some great examples of how to make refactoring work for you.
As a web developer of some years, and having had to pick up othersâ REALLY BAD code many, many times, I can honestly say that when Iâve violated DRY Iâve paid for it in unbillable warranty fixes. The client likes those kinds of surprises far less than a milestone slip, and the accountants are none too sympathetic either. CYA and DRY, every time, all the time. Have the courage to stand for the quality of your work.
This would seem to lend itself to a file-per-method result, no?
Iâm not saying I agree with Uncle Bobâs proposed solution. Note that he used Ruby, so itâs a little more than seperate files-- he added methods dynamically to the class at runtime.
Mainly, Iâm agreeing with his observation that the data layer, and the reporting layer, need to be editable seperately from the employee class. However we choose to achieve that goal is up to us. The âDo One Thingâ of the employee class is the employee, not database details, or reporting details. This does fly in the face of traditional OOP-- the data and the logic âlive togetherâ-- which is why itâs counter-intuitive.
BuggyFunBunny is bang on. Straight from database basics: instead of one data item in one place only, itâs one piece of logic in one place only. The power of OO is that is does so much to enable This One Thing.
The problem with Jeffâs âEmployee classâ example is that itâs at the wrong level of granularity â and thereâs no way to tell what the right level of granularity is.
Suppose we take âDo One Thingâ at face value. Then clearly, each line of source code should Do One Thing. Clearly, each function or method should have a single purpose. Clearly, each object instance and each class should have a single responsibility. Clearly, each module, source file, or DLL should Do One Thing. Clearly, each program and each operating environment should Do One ThingâŚ
Now, at some point these conclusions become obviously absurd; even in the Unix world, where Curlyâs Law has been obeyed unconsciously since the dawn of time, we have âGNU helloâ and OpenOffice.
So the question is, at what point do we admit that âDo One Thingâ is not a fundamental law? Jeff seems to believe that the law applies all the way up to the âclassâ level, but not the âmoduleâ level. Iâd personally set the bar at the âmethodâ level, or maybe lower; and Jeffâs âEmployeeâ class certainly fits in with Curlyâs Law on those levels.
these conclusions become obviously absurd
Not necessarily.
Itâs true that âDo One Thingâ becomes larger and larger as you move up the chain, more nebulous, more difficult to define. The higher up you get (OS level?), the more the One Thing becomes a vision statement rather than a concrete definition you can point to. But Iâd still say itâs true; I think we can somewhat reliably point to the âDo One Thingâ of OS X, Windows Vista, Ubuntu.
I didnât find it until after I wrote this post, but Tim Ottinger has an earlier post he calls Curlyâs Guide To Software with some excellent examples:
http://blog.objectmentor.com/articles/2007/01/10/curlys-guide-to-software
As Tim points out, Do One Thing applied at the line level is one reason I donât care for one-line conditional operators:
return x==1 ? Stuff() : OtherStuff();
I think DRY principle is only one tool for writing good code. Design is about compromises â very often, when DRY requires me to use some clever design pattern i find it more useful to compromise purity in favor of clarity.
If you pick purity as your goal then youâll have less time for hobbies and family!