Quantcast
Channel: User Filip Milovanović - Software Engineering Stack Exchange
Viewing all articles
Browse latest Browse all 167

Answer by Filip Milovanović for What is the right understanding for cohesion and coupling?

$
0
0

Firstly, am I wrong in assuming that just asking about cohesion and coupling with regards to this code is enough to motivate this refactoring, or should I be using other principles, asking other questions?

I would be inclined to say that this example doesn't quite demonstrate the cohesion/coupling concerns specifically; to me it seems to be more about refactoring to an appropriate level of abstraction (as in, the original function deals with a lot of minutia, while the refactored function is composed only out of high-level statements (+ some simple control flow)). In other words, it almost reads like a high-level list of things to be done - you can immediately see the intent, as it is directly expressed (that is, if you had actual names there, instead of "doSomeBusinessLogic"). E.g., the high-level functions could correspond to explanatory "header" comments that might appear in this code, like // Go get configured threshold or // Get things bigger than threshold.

That said, it's hard to see why this type of redesign is a good idea when you only have generic names (like "doSomeBusinessLogic") and no other context (what the operations are matters, how things are expected or observed to change matters, etc). One of the values of keeping all the statements in a function at the same level of abstraction (or same level of detail, if you will) is that now each step has well defined inputs and outputs, so when you dig in, it's easier to think about each step in isolation, and it's also easier to think about the overall flow of the steps without getting into what's happening within each step. That is, if the high-level functions are well designed, it becomes easier to decouple these concerns. And you're right, injecting the data server would be more flexible and it would promote looser coupling, but that's a somewhat separate point.

Second, as far as I understand it the code in the first snippet is cohesive; there's no statement/expression that isn't providing value towards the result of the function - it's all related to the function.

When it comes to cohesion, the problem isn't really apparent if you're just looking at the contents of one function. Sure, a function might have several responsibilities (by some measure) and you may find that undesirable, but in terms of cohesion, that's not the core issue. Code that is not cohesive is spread out across two (or more) places - but you'll often only become aware of this in hindsight. What I mean by that is not that you've split some function in a weird way for funsies, but that your original design (that seemed to organize things neatly and logically) at some point turns out to be flawed in the sense that there are two (or more) places in the codebase (in distinct, supposedly fairly independent classes) that you always need to change together, because otherwise your software won't quite work. E.g., maybe you change a data structure or a parameter list here, or the order of operations there, and this in turn forces you to update the code in these other places. This means that all these affected parts of the codebase implicitly rely on various specific aspects of each other, and are thus coupled - and they cannot avoid this coupling precisely because the code is spread out in several places. That is, the code in question is strongly coupled, but is currently not cohesive (not bundled up together), and that's a problem. The reason why you'd want to try and reshape the code to a more cohesive design has less to do with making the code beautiful or correct or whatever, and more to do with the fact that as you work with this codebase, you're encountering this problem frequently enough that it has become a pain in the a**, excuse my French. (Ideally, you'd want to anticipate it a bit before that, but that requires experience.)

Cohesion is about rethinking the design so that the code that is related in the way described (code that changes together) is brought together to one place, expressed as one concept - e.g. a class (or a component) with a narrowly defined job. Note that, in order to do this redesign, you have to take a good look at what is fundamentally coupled to what and in what way, and what is only incidental - it's not a simple copy-paste thing where you just shuffle the code around. Your new, cohesive component isn't necessarily going to include all of the code that's required to actually get something done. Sometimes, it's going to be a class that requires a number of dependencies plugged into it in order to actually be able to run and do anything. But it encapsulates the rules that were encoded in several places before. Other times, it's going to be a class that only manipulates things internally, and leaves communication with the UI or the DB to its callers. In the redesign process, you have to come up with interfaces and data structures that it exchanges with these dependencies (or callers), such that you end up being able to put all the stuff that's coupled (the high level logic) on one side, and all the code that implements the details of that (code that manipulates general purpose libraries and such) on the other side. You'll also want to design your boundary interfaces and data structures around something that's less likely to change than the code on either side (the abstractions need to be stable), and arrange things in such a way that the more volatile code on either side changes for different reasons (otherwise, you'd still have non-cohesive components, just in a different form).

You'd have some idea of what these interfaces and data structures might be through your experience working on the project, familiarity with previous change requests, the roadmap, project goals, and even due to the fact that there are parts of the codebase that are difficult to work with. That is, it would have to be based to a large degree on your understanding of your specific problem domain. Note also that this is not a magic bullet: this redesign would decouple the code with respect to the kinds of changes that were difficult due to the way code was coupled before, but it might not work so well for other kinds of changes - and it's the job of the designer(s) to figure out if the tradeoff makes sense or not.

Now, a common symptom of non-cohesive code is that some functions end up having several responsibilities, because some of those responsibilities are spread out across several classes/functions that often also deal with other concerns. But again, separation of concerns at the level of one function is only half of the story - you have to take a broader view.

It's hard to come up with a compelling example while keeping the answer length reasonable, but one common situation is when you have a bunch of logic (in different parts of the codebase) that depends on the value of an enum, so you have a bunch of if statements (or switch statements) that typically need to be kept in sync. These might even go across layers. What you then might do is, you first adjust the code a bit so that each case looks kind of the same, so that the logic (or its sequence of high-level operations) becomes easy(er) to generalize into a base class (or an interface) - you then implement each case as a separate subclass, and you pull the aspects that were previously separated (but pertained to the same case) into the hierarchy. You'd then have some sort of factory that selects the appropriate subclass (confining the if statements to one place and to one concern), and from then on you'd work with the class polymorphically. Ideally, the types of changes that previously required editing several files and being careful not to miss a case etc, should now be mostly confined to one subclass. Again, not necessarily easy to get right, and it involves tradeoffs (as any design does).


Viewing all articles
Browse latest Browse all 167

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>