18

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.

lutze
  • 199

7 Answers7

26

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.

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.

Telastyn
  • 110,259
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.

Kramii
  • 14,199
  • 5
  • 46
  • 64
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.

Doc Brown
  • 218,378
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.

Z .
  • 117
  • 4
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.