5

When I do my code layout, I like to start with a rather high level view, and then start to delegate actual computation to class or functions that have a bit more detail. Then in these classes of functions, I do the same, layer by layer, until I get to the bottom where I have to do the 'real' action.

Example:

def build_table():
    legs = get_legs()
    plate = get_plate()
    return put_together(legs, plate)

def get_legs():
    legs = []
    for i in [0,1,2,3]:
        legs.append(get_leg_from_warehouse())
    return legs

def get_plate():
    plate = get_plate_from_warehouse()
    return finish_plate(plate)

def put_together(legs, plate):
    table = Table()
    for i in [0,1,2,3]:
       table.mount(legs[i])
    table.mount(plate)
    return table

class Table:
    self.component = []
    def mount(self, item):
        self.component.append(item)

In this way, I find it easy to think about the layout, and hide complexity. I mostly have short pieces of code that are easy to understand.

The downside of this is, that when I discover I need a piece of information available at the top of the stack further down, I pass arguments from function to function. In the above example, I might gather 'screws' at the top, and then keep passing them down to a function where they are actually drilled into the wood. This makes it feel that it is not so easy to modify the code, and I wondered what I could do about this. In this example the modified code would look like this:

def build_table():
    legs = get_legs()
    plate = get_plate()
    screws = get_screws()
    return put_together(legs, plate, screws)

def get_legs():
    legs = []
    for i in [0,1,2,3]:
        legs.append(get_leg_from_warehouse())
    return legs

def get_plate():
    plate = get_plate_from_warehouse()
    return finish_plate(plate)

def get_screws():
    drive_to_hardwarestore()
    screws = buy_screws()
    drive_home()
    return screws

def put_together(legs, plate, screws):
    table = Table()
    for i in [0,1,2,3]:
       table.mount(legs[i], screws)
    table.mount(plate, screws)
    return table

class Table:
    self.component = []
    def mount(self, item, screws):
        self.component.append((item, screws.pop()))

So besides adding the code for getting screws, I had to modify 4 lines. This would increase linearly with the amount of layers.

How can I refactor? On the other hand, how can I avoid this in the first place? Is my design process 'wrong'?

Isaac
  • 223
  • 1
  • 6
  • Why is the screw available at the top of your call hierarchy? If it "does one job well", it should not even be visible outside the actual screwing-in routine, because its only function in the system is to hold two specific parts together. (Of course, if you are also using the screw as an electric conductor for a different component whose I/O pathway happens to lead through the area, then you have bigger problems than scope issues.) – Kilian Foth Nov 15 '13 at 07:00
  • Because it is expensive to drive to the hardware store and by one screw each time I need one. So I would rather drive there once, buy a large quantity, and then distribute them to everyone in my shop that needs screws. - Well, maybe it still need not be visible at the top, the first one to use a screw could get screws for all others. – Isaac Nov 15 '13 at 07:11
  • 1
    I don't understand where you see a problem - gathering "screws at the top", putting them into a container object and passing that around IMHO does not make the code hard to modify. You claim that, but miss to provide an example for that case - can you please give one, or give a better explanation? – Doc Brown Nov 15 '13 at 07:34
  • Doc Brown: example added. – Isaac Nov 15 '13 at 07:52
  • 1
    @Isaac: note my edit concerning factory classes. – Doc Brown Nov 15 '13 at 08:42

1 Answers1

7

So besides adding the code for getting screws, I had to modify 4 lines. This would increase linearly with the amount of layers.

I think this is a fallacy. When you add another kind of table part, you will still have only to change 4 lines of code in your "object construction layer". The layers above the "object construction" layer in your code will just work with "table" objects, unmodified, as long as they don't have to deal with the new kind of parts. You can pass that table object through an arbitrary number of layers and don't have to change anything. And when you find a part in your code where you have to access your screws, that code can just ask the "table object" for the screws.

Of course, when the object construction process itself gets more complicated, and you think you have "too much passing around" in there, you may consider to create a (table) factory class to encapsulate the constructions. That way, it is possible to create "screws", "legs", and "plate" in one place, store them in member variables of the factory, and get them later in the "mount" function.

So as long as the construction process is simple, stick to your design, and if it gets more complicated, use factory classes.

gnat
  • 21,442
  • 29
  • 112
  • 288
Doc Brown
  • 199,015
  • 33
  • 367
  • 565