17

I'm writing Ruby code for a simple encryption exercise and have frequently run across this dilemma (the exercise is a solitaire cipher if you must know). It is a question of whether I should pad out my logic with descriptive variables and single step statements that make the function readable instead of a concise, even dense statements that eliminate repetition and/or minimizes opportunities for errors.

My most recent example: My program takes input, and because of rigid format guidelines, it can easily determine if input should be encrypted or decrypted. To simplify, once the encryption key and message are converted/generated to be compatible, it's a matter of subtracting the key from the encrypted message or adding the key to an unencrypted message, to get the desired output(think of the key as encryption, message + encryption = code; code - encryption = message). The DRY position says to me that I should convert my encrypted message differently from my unencrypted message so that the function which takes encryption key and applies it to the message never needs to distinguish. I've found that this means I need some nested if statements in the function but the logic appears to be solid. This code, however, is not easily readable. This would require some commenting to be clear.

I could, on the other hand, write two different functions that are called based on a flag that is set when the application determines encryption or decryption. This would be simpler to read but would duplicate the high level function of applying the encryption key to a message (causing it to be encrypted or decrypted).

Should I lean toward readable code, or concise code? Or have I missed another way to get this functionality and satisfy both principles? Is it a position along a scale in which one must consider the purpose of the project and make the best decisions to serve that purpose?

So far, I tend to emphasize concise, DRY code over readable code.

6
  • 2
    Usable first, DRY second, Readable third. Highly usable code results in highly readable consuming code which more easily meets DRY and readable. This is why you want to take nasty complexities and box them up somewhere with a nice API, if they can't be made better; at least the code that interacts with them will be spared from being bad as well with this approach. Apr 10, 2013 at 2:02
  • 4
    some actual code examples may help, i suspect you are missing some 3rd way such as higher order functions to have dry and readable code
    – jk.
    Apr 10, 2013 at 10:19
  • 2
    Not a duplicate. Concise v. readable and DRY v. readable are two very different comparisons. Not being DRY is much more dangerous than not being concise.
    – djechlin
    Apr 10, 2013 at 20:35
  • Just don't fall into the trap that assuming heavily duplicated code is more readable because it falls into a predictable pattern. I suspect it can be easy to think that way, until you've maintained a system that duplicates so heavily that you find subtle bugs in one branch that aren't in the other, nearly identical branches.
    – KChaloux
    Apr 10, 2013 at 20:41
  • I don't understand what you mean by "high level function of applying the encryption key to the message". If you mean that there is overlap between encrypt and decrypt, consider using the extract method refactoring to factor out the common parts. Apr 11, 2013 at 15:35

7 Answers 7

24

DRY is a guideline, not a religion. Those who take it to the point of DRY above all else, have taken it too far.

First and foremost, usable code is paramount. If the code isn't useful and usable, it isn't... and there isn't any point in writing it in the first place.

Secondly, some day, someone will have to maintain your code. If your code isn't maintainable, they will break your "beautiful" dense, concise, DRY design while cursing your name. Don't do this. I've been that person and every time I see a certain name in the code annotation, I shudder.

Making dense code that lack "descriptive variables" and sticking everything into nested ternary expressions with lambdas without any documentation is clever. It is nice to know that you can do it - but don't. Clever code is very difficult to debug. Avoid writing clever code.

Most of the time spent in software is spent in maintenance of the software - not writing it the first time. Write the code so that you (or someone else) can quickly and easily fix bugs and add features as required to it with as few ideal design breaking changes.

2
  • Intuitively or not, you've hit an issue I was overlooking. I have been writing 'clever' code as much as I could for this exercise, just to know I could do it, which I agree is nice to know, but I agree with you here that it is a bad practice. Now, I have a lot of refactoring to do.
    – lutze
    Apr 10, 2013 at 14:31
  • 1
    @lutze I am a perl and sometimes ruby coder and can certainly understand the temptation to write clever code in there. A pre-post version of this answer included a quote from Larry Wall on hubris - I believe that this is a very important virtue when working with code that will some day be maintained. Because of the possible terseness of the languages it is sometimes difficult to get at the advantage of writing more code rather than trying for clever dense code... until you have to maintain it.
    – user40980
    Apr 10, 2013 at 15:08
17

I'm not sure based on the question that you understand DRY. DRY code is not the same as concise. Quite often it is the opposite.

Here, I'm not sure what the big deal is for this example. Make a function to encrypt, a function to decrypt, helpers for common functionality (mix bytes), and a simple front end to take input and determine encrypt/decrypt... No repeating yourself.

In general though, both DRY and readability exist to help maintaining and extending code. Not all scenarios are equal, a big readability hit to remove a little repeat isn't good, and neither is a bunch of duplication to add a little readability.

If pressed, I would prefer readability. Duplicate code can still be tested - unreadable code leads to you doing (and testing) the wrong thing.

1
  • Great points, I had indeed combined the DRY principle a little with the goal of concise code.
    – lutze
    Apr 10, 2013 at 11:48
12

I am unfamiliar with your specific case, but can give some general guidelines.

The purpose of both readability and DRY is maintainability.

Maintainability is important in most situations you'll spend more time maintaining code than writing it. This is especially true if you consider writing code to be a special kind of maintenance.

Unfortunately, DRY is often misunderstood. This is partly because it seems so simple, but like a lot of simple things it can be, well... complicated.

The intention od DRY is that every unit of functionality exists in just one place. If the principle is followed, the code maintainer who is tasked with changing or verifying that functionality can deal with the code in once. If DRY is not followed then there is a very real danger that some copy of the functionality won't be maintained properly.

The most blatent violation of DRY is copy-paste coding, where entire blocks of code are repeated verbatum in the code base. This is surprisingly common in my experience, and no way that does it add to readability. Therefore, refactoring the code to introduce a common method invariably increases DRY conformance and readability.

The second most blatent violation is "copy-paste and change it a little bit". Again, refactoriing to introduce a comon method with parameters, or breaking a function down into steps and abstracting out the commonalities almost always increases readability.

Then there are the more subtle violations, where functionality is duplicated but the code is different. This always isn't easy to spot, but when you see it and refactor to pull the common code into a single method / class / function then the code is ususally more readable than it was before.

Finally, there are the cases of repetition of design. For example, you might have used the state pattern several times in your code base, and you're considering refactoring to eliminate this repetition. In this case, proceed with caution. You may well be reducing readability by introducing more levels of abstraction. At the same time, you're not really dealing with duplication of functiionality but with duplication of abstraction. Sometimes it is worth it... but often it isn't. Your guiding principle will be to ask, "which of these is more maintainable".

Whenever I make these judgement calls I try to consider the amount of time people will spend maintaining the code. If code is core business functionality then it'll probably need more maintenance. In that cases I aim to make the code maintainable for people who are familiar with the code base and the abstractions involved. I'd be happier to introduce a little more abstraction to reduce repetition. In contrast, a script that is rarely used and infrequently maintained might be less easy to grasp for the maintainers if it involves too much abstraction. In that case I would err on the side of repetition.

I also consider the experience level of other members of my team. I avoid "fancy" abstractions with inexperienced developers, but leverage insustry recognised design patterns with more mature groups.

In concusion, the answer to your question is to do whatever makes your code most maintainable. What that means in your scenario is up to you to decide.

3
  • I think you've nailed one of my issues exactly. The example I gave of the functionality I was trying not to duplicate is more likely duplication of abstraction.
    – lutze
    Apr 10, 2013 at 14:13
  • +1. I'm currently dealing with a system that abused copy and paste to a ridiculous degree. Factoring out a single method from copy/pasted code removed over 400 lines from one of my classes today. Unfortunately, I found it in a 900 line method ...
    – KChaloux
    Apr 10, 2013 at 20:43
  • 1
    If two code constructs presently do the same thing, but a change to one should generally not imply a change to the other, copy/paste may be better than factored common functionality. If code uses two character-for-character identical methods do the same thing for different purposes, and consistently bases the choice of method upon the required purpose, then if things later need to be done slightly for one of those purposes, changing just the appropriate method will affect only those behaviors which should be affected. Using a single common method could have made the change much harder.
    – supercat
    Mar 26, 2014 at 20:12
7

If your functions become more complicated and longer (thus less readable) when you try to make them DRY, then you are doing it wrong. You are trying to put too much functionality in one function because you think that this is the only way of avoiding to repeat the same pieces of code in a second function.

Making code DRY almost always means refactoring common functionality to smaller functions. Each of that function should be simpler (and thus more readable) than the original one. To keep everything in the original function on the same level of abstraction, this may also mean to refactor additional parts out which are not used elsewhere. Your original function then becomes a "high level function", calling the smaller ones, and it gets smaller and simpler, too.

Doing this consequently leads mostly to different levels of abstractions in your code - and some people believe such kind of code is less readable. To my experience, this is a fallacy. The key point here is to give the smaller functions good names, introduce well-named data types and abstractions which can be easily grasped by a second person. That way "DRY" and "readability" should only very, very seldom get in conflict.

2

While I'm not certain of exactly what's causing the problem here my experience is that when you find DRY and readability conflicting that says it's time to refactor something out.

0

I would choose readable (= maintainable) instead of DRY any day of the week. In reality both often align, someone who gets the concept of DRY would generally produce (mostly) readable code.

1
  • 2
    without an explanation, this answer may become useless in case if someone else posts an opposite opinion. For example, if someone posts a claim like "I would choose DRY (= maintainable) instead readable of any day of the week", how would this answer help reader to pick of two opposing opinions? Consider editing it into a better shape
    – gnat
    Apr 10, 2013 at 4:51
0

Very very very very very few times in my career have I found a legitimate case that pitts readability against DRY. Make it readable first. Name things well. Copy and paste if need be.

Now step back and look at the totality you have created, like an artist stepping back to look at their painting. Now you can properly identify the repetition.

Repeated code should either be refactored into its own method, or pulled out into its own class.

Repeating code should be a last resort. Readable AND DRY first, failing that readable if you limit the scope of repeated code.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.