Blog of Rob Galanakis (@robgalanakis)

Large initializers/ctors?

With closures (and to some extent with runtime attribute assignments), I find the signatures of my UI types shrink and shrink. A lot of times we have code like this (python, but the same would apply to C#):

class FooControl(Control):
  def __init__(self, value):
    super(FooControl).__init__()
    self.value = value
    self._InitButtons()    

  def _InitButtons(self):
    self.button = Button('Press Me!', parent=self)
    btn.clicked.addListener(self._OnButtonClick)

  def _OnButtonClick(self):
    print id(self.button), self.value

However we can easily rewrite this like so:

class FooControl(Control):
  def __init__(self, value):
    super(FooControl).__init__()
    btn = Button('Press Me!', parent=self)
    def onClick():
      print value
    btn.clicked.addListener(onClick)

Now this is a trivial example. But I find that many types, UI types in particular, can have most or all of these callback methods (like self._OnButtonClick) removed by turning them into inner functions. And then as you turn them into inner functions in init, you can get rid of stored state (self.value and self.button).

But as we take this to the extreme, we end up with very simple classes (and in fact I could replace FooControl with a function, it doesn’t need to be a class at all), but very long init methods (imagine doing all your sub-control creation, layout, AND all callback functionality, inside of one method!).

I’ve decided I’d rather have a long init method, usually broken up into several inner functions, rather than a larger signature on the class with layout, callbacks, and stored state. In my mind, it is easier to pull something out into a type attribute, rather than remove it, as anything on the type is liable to be used externally. And breaking up your layout into instance methods that can really only be called once (_InitButtons), from the init, adds a cognitive burden for me.

So I can justify this decision to eliminate extra attributes rationally, but what seals the deal is, I’m not unit testing any of this code anyway. So whether it is in one long method, or broken up into several methods, it isn’t getting tested.

I started out as very much in the ‘break into small methods’ camp but have wholesale moved into the ‘one giant __init__ with inner functions’ camp. I’m curious what you all prefer and why?

6 thoughts on “Large initializers/ctors?

  1. Daniel says:

    I prefer to keep namespaces uncluttered, especially when your only form of information hiding at the class level is name mangling and politeness on the part of your clients.

    Plus as you say, it’s not much fun to rely on calling a set of methods, quite possibly in a fixed order, before you’ve actually got valid objects. It feels brittle. I quite like using builder-like patterns in compiled languages to allow both division of code and ensure well-formed objects, but that’s not particularly relevant.

    Closures are in general one of the nicer ways to store state in Python, IMO. Were the lambda feature less… interesting?… in this language I’d even balk at inner functions. The function definition boilerplate doesn’t really add anything.

    1. Yeah I wish lambdas looked nicer in python (like, say, C#)… they’re so clunky. However, the very nicer inner def usage makes these larger inner functions much more appealing. Having to write ‘Action inner = (a, b, c) => {…}’ for an inner function is hideous, but IMO ‘(a, b, c) => …’ is really, really nice.

  2. Ben Sizer says:

    It’s hard to read the example as the code loses its formatting. Are you able to fix it?

    1. Done, sorry about that.

  3. Ben Sizer says:

    What stops Python from destroying btn in the latter case? btn may have a reference to FooControl.onClick, but there don’t appear to be any references to btn, which makes me think that whether it gets destroyed or not is down to an implementation detail in the addListener() functionality.

    1. Whoops you’re right. I should be adding ‘btn’ to FooControl somewhere (so it would be “Button(‘PressMe!!’, self)”). I’ve fixed the post. Does it make more sense now?

Leave a Reply