开发者

What is an acid test for "the same level of abstraction" when writing composed functions / methods?

Context: I need to explain "composed methods" to a group of mixed-experience.

I think I heard about it first while reading Beck's Smalltalk Best practices. I've personally not had too many issues writing such methods - however in the local code-wilderness, I've seen quite a few instances where the lack of composed methods had created indecipherable Blobs... and I was in the minority. So I'm taking them through CleanCode - where this one popped up again.

The premise is quite simple.

"Functions should be short, do one thing well and have an intention-revealing name. Each step in the body of the method should be at the same level of abstraction."

What I'm struggling with a check for the "same level of abstraction".. viz.. forgive the pun a bit abstract for beginners.

My current explanation would be similar to SICP's "wishful thinking". (Imagine the ideal set of steps an开发者_Go百科d then worry about implementation/making it happen.").

Does anyone have a better set of rules / an acid test to evaluate your decisions while writing composed methods ?


Same level of abstraction - examples:

 void DailyChores()
 {
    Dust();
    Hoover();
    MopKitchenFloor();
    AddDirtyClothesToWashingMachine(); 
    PlaceDetergentInWashingMachine();
    CloseWashingMachineDoor();
    StartWashingMachine();
    Relax();
 }

Hopefully it should be clear that the WashingMachine saga would be better extracted into a separate method entited WashDirtyLaundry();

Arguably the MopKitchenFloor should also be in a separate method entitled CleanKitchen() as quite likely you would want to extend this in the future to include WashPots(), DefrostFridge() etc.

So a better way would be to write as follows:

 void DailyChores()
 {
    Dust();
    Hoover();
    CleanKitchen(CleaningLevel.Daily);
    WashDirtyClothes();
    Relax();
 }

 void WashDirtyClothes()
 {
    AddDirtyClothesToWashingMachine(); 
    PlaceDetergentInWashingMachine();
    CloseWashingMachineDoor();
    StartWashingMachine();
 }

 void CleanKitchen(CleaningLevel level)
 {
    MopKitchenFloor();
    WashPots();

    if(level == CleaningLevel.Monthly)
    {
       DefrostFridge();
    }
 }

 enum CleaningLevel
 {
    Daily,
    Weekly,
    Monthly
 }

In terms of "rules" to apply to code not following this principle:

1) Can you describe what the method does in a single sentence without any conjunctions (e.g. "and")? If not split it until you can. e.g. In the example I have AddDirtyClothesToWashingMachine() and PlaceDetergentInWashingMachine() as separate methods - this is correct - to have the code for these 2 separate tasks inside one method would be wrong - however see rule 2.

2) Can you group calls to similar methods together into a higher level method which can be described in a single sentence. In the example, all of the methods relating to washing clothes are grouped into the single method WashDirtyClothes(). Or in consideration of rule 1, the methods AddDirtyClothesToWashingMachine() and PlaceDetergentInWashingMachine() could be called from a single method AddStuffToWashingMachine():

 void AddStuffToWashingMachine()
 {
    AddDirthClothesToWashingMachine();
    PlaceDetergentInWashingMachine();
 }

3) Do you have any loops with more than a simple statement inside the loop? Any looping behaviour should be a separate method. Ditto for switch statements, or if, then else statements.

Hope this helps


I take "acid test" to mean you would like some concrete rules that help embody the abstract concept in question. As in "You might have mixed levels of abstraction if..."

You might have mixed levels of abstraction if...

  • you have a variable in the function that is only used for part of the function.
  • you have multiple loops in the function.
  • you have multiple if statements who's conditions are independent of each other.

I hope others will add to the above...


EDIT: Warning - long answer from self-taught guy ahead . . .

In my very humble opinion (I am TOTALLY learning here as well) it seems BonyT and Daniel T. are on the right track. The piece which might be missing here is the design part. While it is safe to say that refactoring/composing will always be a necessity, might it also be as safe to say that (ESPECIALLY with beginners!) proper design up front would be the first, second, and third steps to properly composed code?

While I get what you are asking (a test/set of tests for code composition), I think BonyT is applying the earliest of those tests during the "pseudocode" part of the design process, no?

Obviously, in the early stages of project planning, strong design and experienced coders will sniff out the obvious places ripe for composition. As the work progresses, and the team is beginning to fill these initial code stubs with body, some slightly more obtuse exaples are bound to crop up. BonyT's example presents these first two steps quite well.

I think there comes a point at which experience and a finely-tuned "nose" for code smell comes in - in other words, the part you may not be able to teach directly. However, that is where Daniel T's answer comes in - while one may not be able to develop concrete ACID-type "tests" for proper composition, one CAN employ techniques such as Daniel proposes to seek out potential smelly code. Detect "hints" if you will, that should at least prompt further investigation.

If one is not certain whether things are composed at the proper level, it might make sense to work through a particular function and attempt to describe, step-by-step, what is going on in simple, single sentences without conjuctions. This is probably the most basic ACID-type test that could be performed. Not to mention, this process would by default end up correctly documenting the code . . .

In response to BonyT you imply that his pseudocode/method stubs make the next step obvious - I am betting that if one walks through a function and describes things step by step, one will often find that indeed, the next step either obviously follows at the same level of detail, or belongs elsewhere. While there will obviously be cases (many, with complex code) where things are not so neat, I propose that this is where nothing but experience (and possibly genetics!) come in - again, things you can't teach directly. At that point, one must examine the problem domain, and determine a solution which best fits the domain (and also be prepared to change it down the road . . .). Once again, properly documenting the "in-between" cases with short, simple statements (and narrative describing decisions in gray areas) will help the poor maintenence guy down the road.

I realize that I have proposed nothing new here, but what I had to say was longer than a comment would allow!

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜