The contrast between John's "modules should be deep" approach and "extract a lot of small functions" approach advocated by Bob Martin, Kent Beck, and Martin Fowler, among others (see, for example, https://sites.google.com/site/unclebobconsultingllc/one-thing-extract-till-you-drop) has led me to an idea of immediately executed inner functions which, with some support from IDEs and editors, may resolve this contradiction.I've presented the idea here: https://medium.com/@leventov/semi-function-a-missing-tool-to-handle-complexity-in-imperative-code-bdefe82d8cf6
--
You received this message because you are subscribed to the Google Groups "software-design-book" group.
To unsubscribe from this group and stop receiving emails from it, send an email to software-design-...@googlegroups.com.
To post to this group, send email to software-d...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/software-design-book/CAAMLo%3DaQZEG-PutHJMf5XMJzCThNxEGZM8r030vx9S-3mrS5KQ%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.
> Let's not lose focus on the overall goal, which is to minimize overall
> design complexity. Tips like "do one thing" can help with that goal,
> but we should only use them to the degree that they really do help.
Readability, understandability, minimizing complexity, etc. are of
paramount importance, but there are other aspects of good code/design
that sometimes still take priority (e.g. performance, reliability,
security, modifiability, testability, etc.). Sometimes you need to
sacrifice on simplicity to benefit them (and sometimes you don't).
I'm reminded of something Kent Beck wrote a few years ago along the
lines of "sometimes you have to make a really big mess before you can
make things cleaner."
Which is not to disagree with you but just to point out that sometimes
we *do* need to lose focus on the overall goal.
> Is there anyone out there who would argue that Uncle Bob's result code
> really is easier to understand, overall? If so, I'd be interested to
> hear the reasoning.
In my opinion his final version is more difficult to understand if you
try to read the whole class from start to finish and keep it all in your
head. It's harder to read because you have to jump back and forth
between method definitions, some of the methods are written at one level
of abstraction and others at a different level, you need to read the
code two or three times (or I do at least) in order to grok the full
context of each step in the process, etc.
BUT...
In my experience when I encounter new code on a project in the course of
a specific task I prefer code written in the final style. For example,
imagine that you're asked to change the behavior of SymbolReplacer so
that it only replaces the first occurrence of each symbol. If your
code spelunking leads you to replace() I think the final version is
easier to understand and modify because you don't need to read the
entire class. You can jump straight from replace() to
replaceAllSymbols(), skim the for loop to see we're iterating over
each symbol, jump to replaceAllInstances(), skim the conditional to see
we don't care about its criteria, jump to replaceSymbol() and see that
it's calling String::replace, and you're done. There's no need to look
at nextSymbol(), shouldReplaceSymbol(), symbolExpression() or
translate().
That's the approach I personally take when making changes in large
codebases I'm not familiar with: start at some easily identifiable
entry point like main() or a Servlet or whatever, then start skimming
& jumping around to try and hone in on where some particular behavior
is implemented. In such cases I find the heavily extracted style
easier to read & understand.
My bug hunting approach is a little different, though. If I'm trying
to find & fix a bug in some unknown code like SymbolReplacer I'll take
the same skim & jump approach until I end up in replace(). At that
point, however, I'll start reading the entire class from top to bottom
and try to understand it all. Only after I think I understand the
whole thing will I attempt to hone in on the actual bug via tests or
debugging or printfs or whatever. In that case I would prefer the
original version of the code because I find it easier to read that way.
Obviously, I can't have it both ways, so which would I prefer overall?
I think I'm with Uncle Bob on this one and would prefer the final
version in general. When starting a new project from scratch I would
start writing code in the original style but over time I would expect
to refactor it towards the final style. I just think it eases
maintenance on large, mature codebases if they are written that way.
Especially if the expectation is that every developer on the team can
work on any part of it at any time.
I wonder if that's what Uncle Bob is getting at when he writes: "after
programming for over 40+ years, I’m beginning to come to the conclusion
that this level of extraction is not taking things too far at all. In
fact, to me, it looks just about right." ?
Best,
Kent
String replace() { replaceAllSymbols(); return stringToReplace; }
BUT...
In my experience when I encounter new code on a project in the course of
a specific task I prefer code written in the final style. For example,
imagine that you're asked to change the behavior of SymbolReplacer so
that it only replaces the first occurrence of each symbol. If your
code spelunking leads you to replace() I think the final version is
easier to understand and modify because you don't need to read the
entire class. You can jump straight from replace() to
replaceAllSymbols(), skim the for loop to see we're iterating over
each symbol, jump to replaceAllInstances(), skim the conditional to see
we don't care about its criteria, jump to replaceSymbol() and see that
it's calling String::replace, and you're done. There's no need to look
at nextSymbol(), shouldReplaceSymbol(), symbolExpression() or
translate().
Oh, one more thing: Robert C. Martin is asking at the beginning " What the hell does 'one thing' mean?"
Hmmm, this sounds like exactly what I would do when reading the original code; haven't you had to look at just as much code, but you also had to jump around in the file among several different methods? For example, the jump from replace() to replaceAllSymbols() is pure overhead, because there is no actual functionality in replace; I learned nothing by looking at replace() except that it delegates its entire functionality to another method.
By the way, when I first read replace() I wondered "why is this procedure performing an operation and then returning the original unmodified string?" I eventually had to figure out, without the help of documentation, that this collection of methods actually overwrites stringToReplace with the result (which, by the way, is another bad design decision, because by the time replace() returns, the variable name no longer describes its contents). This is typical of the super-decomposed style: it tends to create subtle and non-obvious dependencies between the zillions of tiny methods.
-John-
This might be the crux of my disagreement with Uncle Bob. To me, "do one thing" refers to the abstraction visible to callers: the method appears to have a single "tight" function (something that can be described and understood very simply). The original code meets this definition.Perhaps Uncle Bob thinks "do one thing" means that a method only has one operation in its body? I don't see any particular value in that definition.
Also, it's not obvious to me that functions that can only be called in one place are a good idea. Isn't the whole idea behind functions that we want to create code that can be reused in many different situations? Use-once functions work against this. Again, I'd ask "how does this help to reduce overall system complexity?"
I read your post and to say the truth I read also your 'stratified design' article since linked. Thank you for you answer. I think I understood your refactoring, and for sure it works, though the fact that both methods receive the same parameter ('stringToReplace') make me suspicious that these two methods might be tightly coupled
Now, the real question is, do we really need a "deep-ness" principle or all the other principles & learnings about software design (R. Martin, K. Beck, M. Feathers, A. Grimm, S. Metz, N. Pryce/S.Freeman, your desk mate, etc.) allow us to produce deep components and functions?
Here's a relevant code review: someone did "Uncle Bob" style refactoring and a reviewer pushed back:tl;dr: the refactoring changed simple tests for determining the type of a numeric literal such asif "e" in text:into calls to functions such asdef is_scientific_notation(text: str) -> bool:
"""Checks if the supplied string is a number with scientific notation"""
return "e" in textIt seems to me that a better solution would simply be a brief commentif "e" in text: # scientific notation
--
You received this message because you are subscribed to the Google Groups "software-design-book" group.
To unsubscribe from this group and stop receiving emails from it, send an email to software-design-...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/software-design-book/CAEoi9W6aBKmuUakssN-PVc_8O0%2B2O8mZj5o-AKhku_8KnCHp0g%40mail.gmail.com.
Two thoughts for the pile:
1. is_scientific_notation("not a number in scientific notation")
-> True. So abstracting the statement 'if "e" in text' creates
a bug if we use this function elsewhere, expecting it to do what
it says it does. Which is a reasonable expectation.
2. This is in a 7k line file basically sitting alone in a
directory, with 40ish classes in it and no comments at the top. I
feel like there is a more coarse refactoring that should take
priority. :D
Paul
To view this discussion on the web visit https://groups.google.com/d/msgid/software-design-book/84c678c5-9edd-477c-840f-1dcd03ac602en%40googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/software-design-book/89eed4d0-85f9-1d4f-fd34-be7ca380c57a%40gmail.com.
-- August
-- August
On Thu, Apr 25, 2019 at 12:41 AM Ralf Westphal <ral...@gmail.com> wrote:Oh, one more thing: Robert C. Martin is asking at the beginning " What the hell does 'one thing' mean?"
This might be the crux of my disagreement with Uncle Bob. To me, "do one thing" refers to the abstraction visible to callers: the method appears to have a single "tight" function (something that can be described and understood very simply). The original code meets this definition.Perhaps Uncle Bob thinks "do one thing" means that a method only has one operation in its body? I don't see any particular value in that definition.
-John-