1

What is the best strategy for maintaining easy to follow code when keeping things DRY means you have to pass a lot of parameters to shared functions?

In my specific case, I have a grails based app, and have implemented a system where to reference object B from object A, I have taglibs, gsp's, javascript and service code to manage that allows the user to click a link, a list of all object B's popup, allow them to select or Add a new 'B' entry and that reference then gets stored in a field in object A, This is being implemented in many different places, parameterising many things.

Keeping this DRY is becoming a bit of a nightmare to maintain, and certainly difficult for any new eyes to understand.

  • 2
    "shared functions"?? maybe you should post some code – Ewan Oct 1 '15 at 14:55
  • 2
    no option to group related parameters? – ratchet freak Oct 1 '15 at 15:11
  • 3
    DRY is not a mandate; it is a guideline. If following any of the usual programming precepts religiously is causing you problems, you're doing it wrong. – Robert Harvey Oct 1 '15 at 15:35
  • 1
    Your question is pretty unclear. Why is DRY the cause of many parameters, and what has that to do with the described feature of your application? – Doc Brown Oct 1 '15 at 19:46
  • 1
    When functions start getting too many parameters, it is not unlikely that holding the DRY principle responsible for this is a misconception. – Doc Brown Oct 2 '15 at 5:45
5

Without seeing any code, it's difficult to give precise advice for your situation. However, your situation is likely highlighting how DRY is the enemy of KISS and loose coupling. To reduce duplication, you have created a set of shared functions. Those functions need many parameters and likely are used in many places. So they are now more complex than they could have been if code were repeated and couple many parts of your application to those functions.

DRY is not a hard-and-fast rule. It's one of many guidelines to creating better code. It needs to be balanced against other conflicting guidelines though. If a little repetition reduces complexity and coupling, then opt for a little repetition.

|improve this answer|||||
  • Thanks, Yes I agree, I believe I've tried to accomplish too many things with one common entry point and that is the issue. - 'One ring to rule them all' does not always have good consequences. There's still plenty of opportunity for me to share functions across the requirement, but by introducing code for each specific entry point should make things much easier to understand. – Dave Oct 2 '15 at 9:00
  • I disagree. DRY is a very important principle and it should be enforced at almost all costs. If your functions take too many parameters, you should group them into an object. – gexicide Nov 21 '15 at 10:20
  • @gexicide: I agree mostly, but from my own experience from larger projects, I have learned the hard way that DRY should only be enforced inside of one software product. If you have different products with different life cycles, you will have to trade off between the DRY principle and entangling the life cycles of two otherwise unrelated software products together. I have seen a case where insisting on beeing DRY by unexperienced people was one of the core factors where a big project failed. – Doc Brown Nov 21 '15 at 14:19
6

The usual advice to avoid repeating the same set of parameters over and over again is to turn parameters that only appear together into a class, and pass an object of that class instead of a list of values. It's perfectly okay to create a class type that merely serves to make your code drier that way.

You sound as if you have the added constraint that the same set of parameters have to flow through code written in different languages. That makes things more difficult, but as long as these values keep appearing together it's often feasible to write that structure down once and then auto-generate classes/structures in different languages from that single declaration. You'd need to post more details about your system to decide whether that's possible, or worthwhile.

|improve this answer|||||
0

The programming language Haskell actually had a similar problem with its "record syntax" for data structures, where you would potentially be updating the third field of a data structure with things like:

incrementCount MyRecord{param1=p1, param2=p2, count=c, param3=p3} = MyRecord{
        param1=p1, param2=p2, count=c+1, param3=p3}

This was solved by adding some language-level syntactic features which look more like updating a dictionary:

incrementCount m@MyRecord{count=c} = m{count = c + 1}

Here the m@<pattern> syntax now supports a "partial pattern" and the count parameter in the record can be specifically referenced and updated. One of the biggest problems that this solves is that you no longer have to update every single function which handles the data structure, merely because you have updated the definition of the data structure to, say, rename key parameters or add new ones.

You can do this too! You just need to pass all of the parameters as one shared parameter dictionary; instead of function (url, queryParams, handlingNotes, ...) you can start to standardize on a request object {url: u, queryParams: q, handlingNotes: h, ...}. Each function which handles the request object will only care about the narrow subset that it cares about and will pass the other parameters right on through, with no boilerplate code needed.

|improve this answer|||||
  • So you're advocating subverting the whole parameter passing/type checking process by passing around dictionaries instead? – Robert Harvey Oct 1 '15 at 16:52
  • @RobertHarvey I mean, Haskell does it type-safely; whether their language will allow them to do likewise depends on their language, no? And yes: sometimes it is more DRY to store parameters in a data structure rather than to pass them directly, especially if those data structures come at the end of a design period after the system has been beat-up a bit. The main cost is that you need a central place to document the data structure when new options are added, but that's already a problem whenever the system has gotten this complex. – CR Drost Oct 1 '15 at 16:59
0

TL;DR: encapsulate the common arguments or save state using partial application or object instantiation.

In the abstract if you want to reduce the number of arguments passed to functions and many of those arguments recur then you can use encapsulation. This can take the form of a data structure (list, hash map, etc). If there is high cohesion between the data and the functions you can also create a class where the functions are methods.

Alternatively, if the arguments are built up over different logical blocks or over time, you can use partial application. This would also allow reuse depending on the how long the data is valid. For example if some data is determined at start up of the application and rarely changes I could use partial application and reuse the resulting function until it does change. If you call many different functions with many of the same arguments you could leverage compose and partial application to manage the code complexity.

FYI: there are certainly other ways of solving this that may work better for you specific example. For example, you may even want to use a Singleton, globals, class variables, etc. There will be different costs and benefits to any code organization technique. Use whatever gives you the smallest implementation that is extensible, maintainable and readable.

|improve this answer|||||

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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