Thank you for a very interesting question! I hope you can bare with me
for a long answer.
First I'd like to point out that there is nothing forbidden about
analyzing your code and create the graph from that. :-)
It is just that we find the naïve approach so much easier when you can
use it. In your case, you seem to not be able to do that. Maybe we are
a bit rabid about the naïve approach in the book and we could probably
balance that a bit.
As you state, the book is very much focused on OOP at the moment.
Would it be a good thing to have more FP in there?
Now, let me try to clarify your problem to see if I understand the code:
You have a function A that you need to change.
A calls function B that calls function C. You want to change A by changing C.
Problem is that B (that uses C) is used by functions X1, X2 and X3
that will break if you change C. If you just change C you have to fix
all the problems with the callers of X1-X3.
If I assume the above is correct I'll try to explain how I see the
design principles from a more FP perspective (with the risk of kicking
in wide open doors):
In my mind you have a straight dependency trail A->B->C. You need to
introduce variability at B->C, since C for A seems to have a different
meaning than C for X1-X3. A good way to introduce variability is the
dependency inversion principle, DIP. AFAIK, in a functional language
the equivalent of inserting an interface in the dependency chain is to
introduce a function as an argument.
In your case you let B take a function with the signature of C. X1-X3
sends the original function C to B. A sends a new function C' that
implements the C signature in the way A needs it to be.
To sum it up regarding the principles: You seem to have two different
responsibilities in C for X1-X3, and the C' that A needs. SRP tells
you to split that i two objects/modules/functions. The DIP tells you
break the dependency chain. Also, you would actually make B adhering
to the OCP, since you have opened it for variation without having to
change it. At least that is how I interpret the principles in the
functional paradigm.
Regarding the Mikado Graph for that code:
If you would be totally naïve you would probably go
Fix A->Fix B-> Fix C -> Fix X1-X3 -> FIx B->Fix C
At this point you should see where the problem is, and start detailing
that solution, possibly guided by the design principle as above. You
can of course go astray (e.g. FIx X1-X3->Fix callers of X1-X3->Fix
callers of callers etc), but the graph can help you backtrace to see
where you went wrong, and help you take some other action that will
lead to results quicker.
I hope this gave answers to some of your questions. Did I miss anything?
I think this email gave us a bunch of ideas of material we need to add
to the book. :-)
Thanks for staying with me this long. I look forward to your comments!
Cheers
Daniel
> --
> You received this message because you are subscribed to the Google Groups "Mikado Method" group.
> To post to this group, send email to mikado...@googlegroups.com.
> To unsubscribe from this group, send email to mikado-metho...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/mikado-method?hl=en.
>
>
--
---------------------------------------------------------
Daniel Brolund
Agical AB - www.agical.com
work: daniel....@agical.com
phone: +46708754002
blog:http://danielbrolund.wordpress.com
twitter: @danielbrolund
private: daniel....@gmail.com
On Tue, Sep 14, 2010 at 17:10, Alan Baljeu <alanb...@gmail.com> wrote:
>
> As an example here, we need to change a function's behavior. To make
> that happen we need to change a called function two levels down. But
> that changes a function one level down, which is in turn used in 3
> other places. We could go in several directions of refactoring things
> to get the works under control, but it requires insight to make the
> right choices here. The wrong choices lead to an infinite regress of
> changes.
I haven't had time to look at the book draft, but I know a little
about your codebase from discussions on other lists.
Assuming we're still in the non-OOP C++ codebase, I think Daniel's
reply in a functional-programming context is a little off. Yours is
procedural rather than functional, I guess?
I wonder if a good way would be to purposefully duplicate the function
two levels down (let's call it f3) so that you have f3 and f3b, where
f3b contains the new behavior.
The root function (f) could trivially be changed to use the new
function f3b, and the rest of the original code could be left as-is.
Assuming you add tests for f and f3 in the process, those two should
be safe targets for refactoring.
Now to attack the duplication... I'd use a combination of Feathers'
hyper-aware editing (was that the term? Anyway, pay close attention
and work with a colleague) and good judgement to extract common pieces
from f3 and f3b into something like f4, and after that exercise, we'd
probably understand the code pretty well. Then it might be worthwhile
to merge them again, based on that understanding.
That said, without tests I don't see many safe changes, but you can
usually learn a lot by putzing around with the code in ways that
cannot possibly change behavior (copy-and-modify - f3->f3b, and
extract commonality - f3, f3b -> f4), often enough to be able to make
earlier-unthinkable changes with a good sense of the risk involved.
Hope that helps,
- Kim
If it is procedural I guess you have to do it either by splitting the
methods so that each call stack has its own set of methods, like I
think Kim is describing, or you have to use flags and conditionals if
that first option gets to wieldy. But I haven't been thinking in
procedural code for many years... I guess I have to think and read
more about the design principles and their application to PP. :-)
Either way, and again as Kim states, this would probably be a good
time to introduce tests. ;-)
Cheers
Daniel
>
> Now by the Mikado concept, we built a graph A - B - C - B - X1, X2 -
> D, and changing C mandated first changing D, X1 and X2.
It seems like you did capture the order of doing things in the graph,
which is good, and given I interpret it correctly. A drawing would of
course be easier to analyze... ;-)
> I don't
> recall change this pattern in your draft, though I may have missed it.
>
I'm not quite sure what you mean here... Is it that this pattern is
not in the book? If so, that is totally fine! :-)
There is always an almost endless number of ways you can change the
code to reach your goal. You always create your own unique, context
specific, sequence of change, and that is basically the outcome of the
Mikado Method. :-)
We have a few patterns in the book that we see more often than others,
but most of the time we make new unique change patterns, for each
situation and context.
Cheers
Daniel
> For more options, visit this group at http://groups.google.com/group/mikado-method?hl=en.
>
>
--
---------------------------------------------------------
Daniel Brolund
Agical AB - www.agical.com
work: daniel....@agical.com
phone: +46708754002
blog:http://danielbrolund.wordpress.com
twitter: @danielbrolund
private: daniel....@gmail.com
On Wed, Sep 15, 2010 at 9:50 PM, Alan Baljeu <alanb...@gmail.com> wrote:
. . .
>
> Another approach, following your suggestion Daniel:
> Introduce a class C_class with a method C_method. We could twin that
> with C1_class and C1_method, and make these a parameter to B
> function. It's not something I'd be inclined to do, as I'm not into
> such mini classes, or the equivalent with function pointers, but
> perhaps it's a good idea to get more into that kind of thing.
>
That is a matter of taste, and maybe more a matter of keeping a
consequent codebase. Depending on the amount of variability needed,
different solutions are more or less appropriate.
Your solution seemed sound. If, by any chance, you will need even more
variability in that point, my suggestion might become more
appropriate. :-)
The Mikado Method should help you make such decisions by visualizing
the consequences of each decision.
Cheers
Daniel
> For more options, visit this group at http://groups.google.com/group/mikado-method?hl=en.
>
>
--
---------------------------------------------------------
Daniel Brolund
Agical AB - www.agical.com
work: daniel....@agical.com
phone: +46708754002
blog:http://danielbrolund.wordpress.com
twitter: @danielbrolund
private: daniel....@gmail.com
On Wed, Sep 15, 2010 at 21:50, Alan Baljeu <alanb...@gmail.com> wrote:
> Thanks for the response Daniel and Kim. (Kim, it seems you're
> everywhere!)
Wherever there's a lesson to be learned about legacy code, I'm there ;-)
> In the case we had here we were able to modify X1-3 so they didn't use
> B. We could test each change independently. Having done that, we
> were free to modify C, B and A without worry about consequently
> breaking anything else because these changes were now isolated to one
> spot of the program.
>
> Thinking out loud here: Our dependency graph was
> A -> B
> X1 -> B
> X2 -> B
> B -> C.
> We had to change C because A needed this change. So we progressed in
> this sequence:
> Create D
> X1 -> D
> X2 -> D
> C -> C'
>
> Now by the Mikado concept, we built a graph A - B - C - B - X1, X2 -
> D, and changing C mandated first changing D, X1 and X2. I don't
> recall change this pattern in your draft, though I may have missed it.
That looks like a variation on what I suggested. One thing that stands
out for me is that it looks like you isolated the code that didn't
need to change (the Xn -> B subtree) instead of the code that needed
changing.
In this case, that looks like more work, because there were two
clients of B that weren't supposed to change and only one that did,
but it could be a factor of the example.
I should probably take a look at the manuscript before attempting
advice in this context :-)
- Kim