< Return to Video

RailsConf 2014 - Writing Small Code by Mark Menard

  • 0:17 - 0:19
    MARK MENARD: So, many thanks to the organizers
  • 0:19 - 0:21
    here at RailsConf. This is my first time
  • 0:21 - 0:24
    talking at RailsConf. It's, frankly,
  • 0:24 - 0:25
    kind of intimidating to be up here
  • 0:25 - 0:28
    and see so many people out there.
  • 0:28 - 0:30
    My name is Mark Menard. I'm gonna be talking
  • 0:30 - 0:33
    about small code today. And I've got a lot
  • 0:33 - 0:35
    of code. About seventy-nine slides. A hundred
    and thirty-seven
  • 0:35 - 0:38
    transitions. Not quite as much as Sandi had,
    but
  • 0:38 - 0:40
    it's a lot to get through. So let's get
  • 0:40 - 0:42
    going.
  • 0:42 - 0:44
    So, I'm just gonna let this quote up there
  • 0:44 - 0:45
    sink in.
  • 0:45 - 0:52
    So. All of us have that file filled with
  • 0:53 - 0:57
    code that you just don't want to open. As
  • 0:57 - 1:01
    you heard earlier, maybe it's your User class.
    That
  • 1:01 - 1:03
    class that has comments like, woe to ye who
  • 1:03 - 1:06
    edit here. The problem with this code is that
  • 1:06 - 1:11
    it does live forever. It encapsulates business
    logic that
  • 1:11 - 1:13
    ends up getting duplicated elsewhere, cause
    no one wants
  • 1:13 - 1:15
    to go in there and look at that code.
  • 1:15 - 1:18
    It's also very hard to understand.
  • 1:18 - 1:20
    I'm gonna be talking about ways to avoid this
  • 1:20 - 1:25
    situation. I'm gonna be talking about code.
    Code at
  • 1:25 - 1:28
    the class level and the method level. Having
    small
  • 1:28 - 1:31
    code at the class and method level is fundamental
  • 1:31 - 1:34
    to being able to create systems that are composed,
  • 1:34 - 1:38
    composed of small, understandable parts.
  • 1:38 - 1:39
    I'm gonna lay out a few base concepts so
  • 1:39 - 1:42
    that we can start with a clean sheet and
  • 1:42 - 1:44
    on the same page. I think there's a lot
  • 1:44 - 1:46
    of problems with what people conceive of as
    small
  • 1:46 - 1:49
    or well-designed code.
  • 1:49 - 1:51
    It's not about the actual amount of code you
  • 1:51 - 1:55
    write, but how the code is organized and the
  • 1:55 - 1:59
    size of the units of code. Fundamentally,
    writing small
  • 1:59 - 2:02
    code is really design discipline, because
    the only way
  • 2:02 - 2:05
    you can write small code is use good design
  • 2:05 - 2:08
    and refactoring.
  • 2:08 - 2:10
    Design and refactoring the way we write small
    code.
  • 2:10 - 2:12
    You can't just sit down and write small code,
  • 2:12 - 2:15
    perfectly well-designed code on the first
    draft. It doesn't
  • 2:15 - 2:18
    work that way. It's iterative process.
  • 2:18 - 2:23
    So what do I mean by small? It's not
  • 2:23 - 2:28
    about total line count. Well-designed code
    will typically have
  • 2:28 - 2:31
    more lines of code than bad code. Just the
  • 2:31 - 2:34
    overhead of declaring methods and classes
    is gonna increase
  • 2:34 - 2:36
    your line count.
  • 2:36 - 2:39
    It's not about method count. Well-factored
    code's gonna have
  • 2:39 - 2:46
    more, smaller methods. It's not about class
    count. Well-designed
  • 2:48 - 2:51
    code is almost definitely going to have more
    classes
  • 2:51 - 2:53
    than what I call undesigned code.
  • 2:53 - 2:58
    Although I've seen some cases of over-abstraction,
    I find
  • 2:58 - 3:01
    that's pretty rare unless someone goes pattern
    crazy. So
  • 3:01 - 3:03
    small code is definitely not about decreasing
    the number
  • 3:03 - 3:08
    of classes in your system. It's about well-designed,
    it's
  • 3:08 - 3:12
    about well-designed classes that aren't poorly
    designed.
  • 3:12 - 3:14
    So what do I mean by small? Small methods,
  • 3:14 - 3:17
    small classes. Small methods are the foundation
    of writing
  • 3:17 - 3:21
    small code. Without the ability to decompose
    large methods
  • 3:21 - 3:25
    into small methods, we cannot write small
    code. And
  • 3:25 - 3:28
    without small methods, we can't raise the
    level of
  • 3:28 - 3:31
    abstraction.
  • 3:31 - 3:33
    To write small code, we have to be able
  • 3:33 - 3:36
    to decompose large classes into smaller classes,
    and abstract
  • 3:36 - 3:40
    responsibilities out of them and separate
    them on higher-level,
  • 3:40 - 3:44
    and base them on higher-level abstractions.
  • 3:44 - 3:46
    It's important that our classes are small,
    because small
  • 3:46 - 3:53
    classes are what lead to reusability and composability.
  • 3:53 - 4:00
    So, why should we strive for small code? Why
  • 4:01 - 4:05
    is it important?
  • 4:05 - 4:09
    We don't know what the future is going to
  • 4:09 - 4:16
    bring. Your software requirements are going
    to change. Software
  • 4:16 - 4:19
    must be amenable to change. Any system of
    software
  • 4:19 - 4:23
    that's going to have a long, successful life,
    is
  • 4:23 - 4:28
    going to change significantly. Small code
    is simply easier
  • 4:28 - 4:32
    to work with than large, complex code. If
    the
  • 4:32 - 4:35
    requirements of your software are never gonna
    change, you
  • 4:35 - 4:38
    can ignore everything that I have to say here.
  • 4:38 - 4:45
    But I doubt that that's the case.
  • 4:45 - 4:48
    We should write small code because it helps
    us
  • 4:48 - 4:52
    raise the level of abstraction in our code.
    It's
  • 4:52 - 4:54
    one of the most important things we do to
  • 4:54 - 5:00
    create readable, understandable code. All
    good design is really
  • 5:00 - 5:05
    driving toward expressing ubiquitous language
    of our problem domain
  • 5:05 - 5:08
    in our code.
  • 5:08 - 5:11
    The combination of small methods and small
    classes is
  • 5:11 - 5:14
    going to help us raise that level of abstraction
  • 5:14 - 5:21
    and express those higher-level domain concepts.
  • 5:21 - 5:23
    We should also write small code so we can
  • 5:23 - 5:28
    effectively use composition. Small classes
    and small methods compose
  • 5:28 - 5:32
    together well. As we compose instances of
    small objects
  • 5:32 - 5:37
    together, our systems will become message-based.
    In order to
  • 5:37 - 5:40
    build systems that are message-based, we have
    to use
  • 5:40 - 5:45
    delegation. And small, composable parts. Small
    code makes small
  • 5:45 - 5:48
    composable parts. It's gonna help our software
    have flexibility
  • 5:48 - 5:50
    and lead to a suppleness over time, and allow
  • 5:50 - 5:53
    us to follow those messages. And eventually
    we're gonna
  • 5:53 - 5:58
    see, find our duck types.
  • 5:58 - 6:01
    And all this is about enabling future change.
    And
  • 6:01 - 6:07
    accommodate the future requirements without
    a forklift replacement.
  • 6:07 - 6:12
    So the goal: small units of understandable
    code that
  • 6:12 - 6:18
    are amenable to change.
  • 6:18 - 6:22
    Our primary tools are extract method and extract
    class.
  • 6:22 - 6:25
    Longer methods are harder to understand than
    short methods.
  • 6:25 - 6:27
    And most of the time, we can shorten a
  • 6:27 - 6:31
    method simply by using the abstract method
    refactoring. I
  • 6:31 - 6:35
    use this thing all the time when I'm coding.
  • 6:35 - 6:36
    And once we have a set of methods that
  • 6:36 - 6:40
    are coherent around a concept, then we can
    look
  • 6:40 - 6:43
    to abstract those into a separate class and
    move
  • 6:43 - 6:47
    the methods to that new class.
  • 6:47 - 6:50
    So, I'm gonna be using the example of a
  • 6:50 - 6:54
    command line option parser that handles booleans
    to start
  • 6:54 - 6:56
    with, and then we're gonna see where the future
  • 6:56 - 6:57
    takes us.
  • 6:57 - 7:02
    So, with the command line, I want to be
  • 7:02 - 7:06
    able to run some Ruby program dash v. And
  • 7:06 - 7:12
    handle boolean options. That's where we're
    gonna start.
  • 7:12 - 7:14
    In my Ruby program, I want to define what
  • 7:14 - 7:21
    options I'm looking for, using this simple
    DSL. And
  • 7:21 - 7:23
    then I want to be able to consume it
  • 7:23 - 7:27
    like this. If options.has and then a particular
    option,
  • 7:27 - 7:28
    I do something.
  • 7:28 - 7:34
    Putting it all together. The DSL, the program
    at
  • 7:34 - 7:36
    the top, the DSL, and then how we actually
  • 7:36 - 7:43
    consume that options object. Pretty simple.
  • 7:43 - 7:48
    Here's my spec. It's pretty simple. It's true
    if
  • 7:48 - 7:50
    the option is defined and it's present on
    the
  • 7:50 - 7:54
    command line. And it's false if it's not.
  • 7:54 - 7:56
    So I run my specs and I get two
  • 7:56 - 8:03
    failures. Yes, I used TDD. So, here's my implementation
  • 8:05 - 8:09
    that fits on one slide. Pretty simply, I store
  • 8:09 - 8:13
    the defined options in an array, and I store
  • 8:13 - 8:17
    the arguments, the argv for later reference.
    Then I
  • 8:17 - 8:19
    have a has method that checks to see if
  • 8:19 - 8:21
    the option is defined. If it's present in
    the
  • 8:21 - 8:22
    argv.
  • 8:22 - 8:25
    And then I've got my option method, which
    implements
  • 8:25 - 8:31
    my simple DSL. Nice and readable. Fits on
    one
  • 8:31 - 8:35
    slide. Probably very comprehendable.
  • 8:35 - 8:40
    So I run my tests. Zero failures. They pass.
  • 8:40 - 8:46
    I'm done. I get to go home until the
  • 8:46 - 8:49
    future comes along. And my workmate comes
    along and
  • 8:49 - 8:52
    says, hey, I really like that library. But,
    could
  • 8:52 - 8:54
    we handle string options?
  • 8:54 - 9:00
    Sounds pretty simple. Pretty straightforward.
    So I think about
  • 9:00 - 9:01
    that, and I come up with a small extension
  • 9:01 - 9:04
    to the DSL, to just pass a second argument
  • 9:04 - 9:06
    as an option with a symbol representation
    of the
  • 9:06 - 9:08
    option type. String, in this case.
  • 9:08 - 9:10
    I also default to being a boolean so I
  • 9:10 - 9:12
    don't have to change the code that other people
  • 9:12 - 9:15
    have done.
  • 9:15 - 9:19
    So, a string option. It's a little different
    than
  • 9:19 - 9:23
    a boolean. It actually requires content. So
    now I
  • 9:23 - 9:27
    need the concept of validation. If the string
    option
  • 9:27 - 9:28
    is missing the content, it's not valid. There's
    no
  • 9:28 - 9:31
    string there.
  • 9:31 - 9:33
    So, then I'm gonna normalize how I get the
  • 9:33 - 9:36
    values out of both those string options and
    those
  • 9:36 - 9:38
    boolean options. You know, that value. This
    is gonna
  • 9:38 - 9:41
    change the API, but sometimes you actually
    need to
  • 9:41 - 9:44
    break the API to enable the future.
  • 9:44 - 9:47
    And I'm doing it pretty early. I've only got
  • 9:47 - 9:51
    one guy in my office using the library at
  • 9:51 - 9:53
    the moment. So, again, putting it all together.
    I
  • 9:53 - 9:55
    can pass the options on the command line.
    I
  • 9:55 - 9:58
    define the options with the DSL, and here's
    how
  • 9:58 - 10:00
    I use my valid? and my value methods to
  • 10:00 - 10:03
    find out, get, find out if it's valued and
  • 10:03 - 10:06
    get my values out.
  • 10:06 - 10:10
    So, now here's the class that implements it.
    Again,
  • 10:10 - 10:13
    on one slide. Probably not as readable. Probably
    not
  • 10:13 - 10:16
    as comprehensible. We're going down what I
    call kind
  • 10:16 - 10:20
    of the undesigned path. It's not too big.
    Thirty-one
  • 10:20 - 10:22
    lines. But it's got issues.
  • 10:22 - 10:24
    It's got a method that's definitely large.
    One that's
  • 10:24 - 10:28
    looking on the verge of being large. It's
    got,
  • 10:28 - 10:30
    for only handling booleans and strings, it
    has quite
  • 10:30 - 10:34
    a bit of, of conditional complexity in it
    already.
  • 10:34 - 10:36
    And as we're soon gonna see, it's not very
  • 10:36 - 10:38
    amenable to change.
  • 10:38 - 10:40
    So we'll look at the pieces and how they
  • 10:40 - 10:41
    work, just so yo understand it.
  • 10:41 - 10:43
    That's my initialize method. It creates a
    hash to
  • 10:43 - 10:45
    store the options. Because we have to store
    the
  • 10:45 - 10:47
    type now, not just that we have an option.
  • 10:47 - 10:50
    It's either boolean or string. And the rest
    of
  • 10:50 - 10:55
    the initialization is the same as it was before.
  • 10:55 - 10:57
    And the valid method, we gotta iterate over
    the
  • 10:57 - 11:00
    options, looking to see which ones are strings.
    So
  • 11:00 - 11:03
    we're doing checking on type here. And, and
    trying
  • 11:03 - 11:06
    to see whether they're present and they actually
    have
  • 11:06 - 11:07
    content.
  • 11:07 - 11:09
    Currently, string options are the only ones
    that need
  • 11:09 - 11:13
    to validate. Boolean options, there's nothing
    really to validate.
  • 11:13 - 11:15
    Either it's there or it's not. No validation.
    But
  • 11:15 - 11:16
    strings, we have to.
  • 11:16 - 11:18
    And the value method, it does a lot of
  • 11:18 - 11:21
    stuff. Let's just pretend for a moment this
    method
  • 11:21 - 11:23
    is a black box. We're gonna come back to
  • 11:23 - 11:26
    it later. Cause this is, by far, the worst
  • 11:26 - 11:29
    code in this current example.
  • 11:29 - 11:34
    But, everything is spec'd. And all my specs
    are
  • 11:34 - 11:36
    green.
  • 11:36 - 11:40
    So let's talk about methods. Cause we've got
    some
  • 11:40 - 11:42
    big ones and we need to clean them up.
  • 11:42 - 11:48
    I call it the first rule of method, of
  • 11:48 - 11:52
    methods. Do one thing. Do it well. Do only
  • 11:52 - 11:52
    one thing.
  • 11:52 - 11:55
    Harkens back to that Unix philosophy of tools
    that
  • 11:55 - 11:59
    you string together with standard in, standard
    out. But
  • 11:59 - 12:01
    how do we determine if a method is actually
  • 12:01 - 12:05
    only doing one thing? This is where your level
  • 12:05 - 12:08
    of abstraction and the abstract, abstractions
    in your code
  • 12:08 - 12:11
    come into play. And you need to develop a
  • 12:11 - 12:13
    feel for this over time. That you want one
  • 12:13 - 12:16
    level of abstraction per method.
  • 12:16 - 12:19
    If all of our statements are the same level
  • 12:19 - 12:23
    of abstraction, and they're coherent around
    a purpose, then
  • 12:23 - 12:26
    I consider that to be doing one thing. Doesn't
  • 12:26 - 12:28
    mean it has to be one line in a
  • 12:28 - 12:30
    method.
  • 12:30 - 12:32
    I can't tell you how many times I've looked
  • 12:32 - 12:36
    at code and seen a comment on a method
  • 12:36 - 12:38
    that was, like, an excellent description of
    what the
  • 12:38 - 12:40
    method did, and if you just took those words,
  • 12:40 - 12:42
    bound together, they'd make a fantastic method
    name. But
  • 12:42 - 12:45
    yet the method is named something else that
    isn't
  • 12:45 - 12:51
    that descriptive. So use descriptive names.
    It's really critical.
  • 12:51 - 12:53
    And the fewer arguments, the better. My personal
    goal
  • 12:53 - 12:58
    is zero arguments on methods. One is OK. Two
  • 12:58 - 13:00
    or three. That's when I start to think I've
  • 13:00 - 13:03
    probably missed an abstraction in my code
    and I
  • 13:03 - 13:07
    should go back and look at it.
  • 13:07 - 13:10
    Separate queries from commands. If you query
    something and
  • 13:10 - 13:12
    it looks like a query method and it changes
  • 13:12 - 13:14
    the state of your object, it's hard to reason
  • 13:14 - 13:17
    about, and people who consume your library
    will be
  • 13:17 - 13:21
    confused by that. So separate those.
  • 13:21 - 13:25
    And, don't repeat yourself. I know Sandi talked
    about
  • 13:25 - 13:28
    this earlier, and it does take some judgment
    to
  • 13:28 - 13:31
    know when it is time to remove the repetition.
  • 13:31 - 13:33
    But you don't want to leave repetition over
    the
  • 13:33 - 13:36
    long term, because it will come back to bite
  • 13:36 - 13:37
    you.
  • 13:37 - 13:42
    So, let's look at our methods. We've got repetition
  • 13:42 - 13:44
    here. Both valid? and value are digging through
    the
  • 13:44 - 13:47
    argv array to find the options from the command
  • 13:47 - 13:50
    line. This is the perfect candidate for an
    extract
  • 13:50 - 13:54
    method, abstraction. Refactoring.
  • 13:54 - 13:57
    We have magic constants scattered around,
    and those are
  • 13:57 - 14:02
    a strong indication that we've missed something.
    An abstraction.
  • 14:02 - 14:06
    We're violating some other rules. It's hard
    to say
  • 14:06 - 14:11
    either of these methods is really doing one
    thing.
  • 14:11 - 14:14
    The code is definitely not at the same level
  • 14:14 - 14:18
    of abstraction. Values digging, valid? is
    digging into the
  • 14:18 - 14:21
    argv array and value is figuring out different
    divergent
  • 14:21 - 14:24
    types and how to return their values. So we're
  • 14:24 - 14:26
    gonna eliminate some of the repetition with
    the extract
  • 14:26 - 14:29
    method refactoring.
  • 14:29 - 14:33
    The extract method refactoring entails moving
    a part of
  • 14:33 - 14:35
    a method into a new method, with a descriptive
  • 14:35 - 14:38
    name, that's the naming part. And then calling
    the
  • 14:38 - 14:40
    new method.
  • 14:40 - 14:42
    This, this refactoring helps us keep the level
    of
  • 14:42 - 14:48
    abstraction consistent in the method we're
    abstracting from. Here
  • 14:48 - 14:49
    we have one expression on a method that's
    a
  • 14:49 - 14:53
    high level of abstraction, and two statements
    that are
  • 14:53 - 14:57
    a low level of abstraction. So we move the
  • 14:57 - 14:59
    less abstract code to a new method with a
  • 14:59 - 15:02
    descriptive name, and then we call the new
    method.
  • 15:02 - 15:05
    And this results in the old method, method
    having
  • 15:05 - 15:10
    a consistent level of abstraction. So back
    to our
  • 15:10 - 15:14
    CommandLineOptions class, both valid? and
    value are digging through
  • 15:14 - 15:17
    the argv collection to find the option value.
    So
  • 15:17 - 15:19
    we're gonna abstract that code and get the
    raw
  • 15:19 - 15:22
    value out of argv.
  • 15:22 - 15:24
    Then we call the method from where the original
  • 15:24 - 15:28
    logic was abstracted. Pretty simple. But now
    the code
  • 15:28 - 15:30
    left behind in valid? and value says what
    I
  • 15:30 - 15:34
    want. Not how to do it.
  • 15:34 - 15:38
    The how has been moved to the abstracted method,
  • 15:38 - 15:40
    raising the level of abstraction just a little
    bit
  • 15:40 - 15:44
    in valid? and value.
  • 15:44 - 15:47
    I'm going to do two more abstractions. I've
    abstracted
  • 15:47 - 15:52
    the string option value method and the abstract
    content
  • 15:52 - 15:55
    method. The naming of the abstracted methods
    is very
  • 15:55 - 16:01
    important. They say what they do.
  • 16:01 - 16:03
    But overall, I'm not happy with this code.
    It
  • 16:03 - 16:06
    is more explanatory, but it's fairly complex
    and hard
  • 16:06 - 16:10
    to understand. It's also not as small as it
  • 16:10 - 16:13
    could be. The methods are large because I
    missed
  • 16:13 - 16:17
    an abstraction. And we're gonna go find that
    now.
  • 16:17 - 16:20
    I'm referencing the option type symbol to
    see if
  • 16:20 - 16:25
    it's a string, which, that's a big smell.
    Then
  • 16:25 - 16:26
    there are the magic constants used to dig
    into
  • 16:26 - 16:29
    the argv element to find the constant within
    that
  • 16:29 - 16:34
    particular string, the substring. If I was
    confident that
  • 16:34 - 16:37
    I'd have no future added requirements for
    this class,
  • 16:37 - 16:41
    I might leave this alone. It works. It's tested.
  • 16:41 - 16:43
    Until my buddy comes to me and says, hey,
  • 16:43 - 16:45
    I really like that library, but could we handle
  • 16:45 - 16:48
    integers now?
  • 16:48 - 16:51
    I could keep driving down this undesigned
    path I've
  • 16:51 - 16:54
    been following, and complicate the valid?
    and value methods
  • 16:54 - 16:56
    by switching on the type of the option and
  • 16:56 - 17:00
    digging into those argv elements to find the
    value.
  • 17:00 - 17:04
    But, this is our chance to make a break.
  • 17:04 - 17:07
    And make our code more amenable to change.
  • 17:07 - 17:11
    But, to illustrate the point, I'm gonna show
    you
  • 17:11 - 17:15
    that undesign method, to show you the OO design
  • 17:15 - 17:18
    actually matters. So we're gonna look at this.
  • 17:18 - 17:20
    This is the undesigned, non OO version of
    this
  • 17:20 - 17:24
    code. Is it horrible? I'll leave that to you
  • 17:24 - 17:27
    to decide. Is it small? In my opinion, definitely
  • 17:27 - 17:30
    not. It is not small, by any measure. The
  • 17:30 - 17:32
    class is growing due to changes in specification.
    The
  • 17:32 - 17:36
    valid? and value methods are being changed
    in lock
  • 17:36 - 17:39
    step. That's a sure sign we've missed an abstraction
  • 17:39 - 17:42
    or a duck type. And those methods are getting
  • 17:42 - 17:46
    big and complicated. And now they're doing
    even more
  • 17:46 - 17:47
    things.
  • 17:47 - 17:52
    And we're just doing booleans, strings, and
    integers. Not
  • 17:52 - 17:53
    that much.
  • 17:53 - 18:00
    The code has tests. They all pass. That's
    good.
  • 18:00 - 18:04
    But it's not satisfying. We've got those large
    methods
  • 18:04 - 18:08
    and complex conditional logic. It's time to
    refactor now.
  • 18:08 - 18:11
    To make the change easy.
  • 18:11 - 18:12
    And now we've got the tests that are back,
  • 18:12 - 18:15
    so we can do it without fear.
  • 18:15 - 18:18
    And, I want to call your attention to a
  • 18:18 - 18:22
    pattern that clearly emerges when we go down
    the
  • 18:22 - 18:26
    non OO path here. We see checking the option
  • 18:26 - 18:31
    type and divergent behavior based on the type.
    Don't
  • 18:31 - 18:36
    reinvent the type system. If you have ducks,
    let
  • 18:36 - 18:38
    them quack.
  • 18:38 - 18:41
    In this example, the option types of boolean,
    string,
  • 18:41 - 18:44
    and integer, those are our ducks. And I'll
    bet
  • 18:44 - 18:48
    there's ducks in your code yearning to be
    free.
  • 18:48 - 18:51
    And just a further confirmation that we're
    dealing with
  • 18:51 - 18:55
    an abstraction or a duck, we see the testing
  • 18:55 - 18:59
    option type again in the value method. Hidden
    inside
  • 18:59 - 19:02
    the valid? and value method, there's a case
    statement
  • 19:02 - 19:05
    here. It just didn't evolve that way as I
  • 19:05 - 19:06
    was writing the code.
  • 19:06 - 19:09
    I'm gonna show you that. You're gonna see
    that
  • 19:09 - 19:13
    it's really clear now.
  • 19:13 - 19:15
    Now it should be really obvious what the duck
  • 19:15 - 19:17
    type is. If you have case statements like
    this
  • 19:17 - 19:24
    in your code, you've missed an abstraction.
    Here, again,
  • 19:25 - 19:27
    we clearly see the duck type.
  • 19:27 - 19:30
    Now, I would guess, if I was writing this,
  • 19:30 - 19:32
    as soon as I had the string type, I
  • 19:32 - 19:35
    would have gone down the OO path. I just
  • 19:35 - 19:39
    wanted to illustrate to you what an undesigned,
    non
  • 19:39 - 19:42
    OO mess you can get yourself into if you
  • 19:42 - 19:46
    keep riding the horse until it's dead.
  • 19:46 - 19:47
    My dad had a saying hanging on his wall
  • 19:47 - 19:50
    in his office. When the horse is dead, get
  • 19:50 - 19:52
    off.
  • 19:52 - 19:55
    But sometimes we don't realize the horse is
    dead
  • 19:55 - 19:58
    and we just keep trying to go. Now it's
  • 19:58 - 20:01
    time to take a fresh look at this. So,
  • 20:01 - 20:04
    since class is the fundamental organizational
    unit we have
  • 20:04 - 20:07
    to work with, it's time to work at what
  • 20:07 - 20:11
    constitutes a good class. Which principles
    are gonna lead
  • 20:11 - 20:14
    us to be able to write small classes.
  • 20:14 - 20:20
    So, how do we write small classes? To make
  • 20:20 - 20:24
    small classes, I think, and this is not just
  • 20:24 - 20:25
    my opinion. It's a lot of peoples' opinion.
    The
  • 20:25 - 20:29
    most important thing we should assure is that
    our
  • 20:29 - 20:35
    class has one responsibility. And that it
    has small
  • 20:35 - 20:38
    methods.
  • 20:38 - 20:41
    All the properties of a class should be cohesive
  • 20:41 - 20:45
    to the abstraction that the class is modeling.
    If
  • 20:45 - 20:46
    you have properties that you only use in one
  • 20:46 - 20:50
    or two methods, that's probably something
    else that shouldn't
  • 20:50 - 20:53
    be in there.
  • 20:53 - 20:55
    Finding a good name for a class will also
  • 20:55 - 20:59
    help us keep it focused on a single responsibility.
  • 20:59 - 21:01
    I sometimes talk to the class. Have you ever
  • 21:01 - 21:04
    heard the concept of talking to the rubber
    duck?
  • 21:04 - 21:06
    Or just explaining your problem to someone?
    They don't
  • 21:06 - 21:08
    even have to respond, and it helps you figure
  • 21:08 - 21:09
    it out.
  • 21:09 - 21:11
    Sometimes I just ask my class, hey class,
    what
  • 21:11 - 21:14
    do you do? And if it comes out with
  • 21:14 - 21:16
    a long list, you've got a problem.
  • 21:16 - 21:19
    So, the main tools we're gonna use to create
  • 21:19 - 21:24
    new classes from existing code, not from scratch,
    but
  • 21:24 - 21:27
    from existing code, is the extract class and
    move
  • 21:27 - 21:30
    method refactorings, which we're gonna go
    through here.
  • 21:30 - 21:36
    So, those characteristics of well-designed
    class. Single responsibility. Cohesive
  • 21:36 - 21:39
    around a set of properties. Additionally,
    it has a
  • 21:39 - 21:43
    small public interface that, preferably, handles
    a handful of
  • 21:43 - 21:47
    methods at the most. That it implements a
    single
  • 21:47 - 21:50
    use-case, if possible, and that the primary
    logic is
  • 21:50 - 21:53
    expressed in a composed method.
  • 21:53 - 21:54
    That last one, I'm not gonna be covering the
  • 21:54 - 21:57
    composed method. That's a whole nother talk.
    But you
  • 21:57 - 21:59
    should check that practice out. It can really
    clarify
  • 21:59 - 22:03
    code and make it much, much more understandable.
  • 22:03 - 22:05
    So, let's look at the code we should have
  • 22:05 - 22:07
    been driving towards as soon as the string
    option
  • 22:07 - 22:11
    type showed up. We're gonna imagine right
    now that
  • 22:11 - 22:13
    we have a string sheet, and we can write
  • 22:13 - 22:16
    CommandLineOptions the way we would have with
    the knowledge
  • 22:16 - 22:19
    that we have now.
  • 22:19 - 22:23
    That needs to support boolean, string, and
    integer options.
  • 22:23 - 22:27
    And remember, we have our tests at our back,
  • 22:27 - 22:30
    making sure that we don't break anything.
  • 22:30 - 22:34
    And, here was my first take at it on
  • 22:34 - 22:38
    what I'd write. The class is twenty-eight
    lines long.
  • 22:38 - 22:41
    It is cohesive around the properties. When
    we're done,
  • 22:41 - 22:43
    most of the methods are gonna deal with the,
  • 22:43 - 22:47
    the hash of options and the array of args.
  • 22:47 - 22:51
    It has a single primary responsibility. Manage
    a collection
  • 22:51 - 22:54
    of option objects.
  • 22:54 - 22:58
    So now we've introduced a collaborator. It
    also manufactures
  • 22:58 - 23:01
    the option objects, which I could abstract
    to another
  • 23:01 - 23:03
    class. But for the moment, I'm gonna leave
    it.
  • 23:03 - 23:06
    If I find it hurts in the future, then
  • 23:06 - 23:09
    I'll change it. That's my general rule. My
    guideline.
  • 23:09 - 23:13
    Is I refactor when it hurts. When making a
  • 23:13 - 23:16
    change hurts, that's the time to refactor.
  • 23:16 - 23:20
    My CommandLineOptions class has a small public
    interface. Just
  • 23:20 - 23:23
    two methods, valid? and value. And it has
    no
  • 23:23 - 23:27
    hard-coded external dependencies yet. I could
    mess that up
  • 23:27 - 23:30
    and introduce those, but we're gonna avoid
    that.
  • 23:30 - 23:32
    Another interesting characteristic is that,
    is that there are
  • 23:32 - 23:36
    no conditional statements in this class, and
    we're gonna
  • 23:36 - 23:41
    keep it that way. In Sandi Metz's 2009 Gerupo??
  • 23:41 - 23:45
    talk, on the Solid Principles, she said something
    along
  • 23:45 - 23:48
    the lines of, a conditional in an OO language
  • 23:48 - 23:50
    is a smell.
  • 23:50 - 23:54
    And that's a really powerful statement. I
    don't think
  • 23:54 - 23:57
    Sandi's saying that we can't use conditionals
    in our
  • 23:57 - 24:02
    code, but that we use conditionals to hide
    abstractions.
  • 24:02 - 24:05
    To hide our ducks.
  • 24:05 - 24:07
    The first time I saw that talk, I don't
  • 24:07 - 24:08
    even know if I heard her say it. It
  • 24:08 - 24:11
    was when I went back and rewatched it. I
  • 24:11 - 24:14
    thought, really? Then, as the years have gone
    on
  • 24:14 - 24:16
    and I've been working, I've gotten to the
    point
  • 24:16 - 24:19
    where I agree with her.
  • 24:19 - 24:21
    If you have a lot of conditionals in a
  • 24:21 - 24:24
    class, you have probably missed a concept
    that should
  • 24:24 - 24:29
    be abstracted out of it.
  • 24:29 - 24:32
    So the initialize and option method from our
    previous
  • 24:32 - 24:35
    implementation carry over unchanged. Except
    that we're gonna store
  • 24:35 - 24:37
    the options in a hash instead of just the
  • 24:37 - 24:40
    type.
  • 24:40 - 24:42
    My valid? method now simply asks all the options
  • 24:42 - 24:45
    if they're valid, and the value method simply
    looks
  • 24:45 - 24:48
    up the option hash and asks it for its
  • 24:48 - 24:52
    value. So, now we need to build the options.
  • 24:52 - 24:54
    We have to implement this. And this is where
  • 24:54 - 24:57
    we're gonna instantiate the objects that represent
    the boolean,
  • 24:57 - 24:59
    string, and integer options.
  • 24:59 - 25:04
    So, now we have the CommandLineOption class,
    we need
  • 25:04 - 25:07
    collaborators. In order to get anything done,
    CommandLineOption needs
  • 25:07 - 25:11
    option classes to manage. It's gonna have
    those objects.
  • 25:11 - 25:14
    So this is creating a dependency. And if we're
  • 25:14 - 25:16
    gonna create a dependency in our code, we
    can
  • 25:16 - 25:18
    do it in a way that's amenable to change,
  • 25:18 - 25:19
    or we can do it in a way that's
  • 25:19 - 25:26
    gonna make it hurt in the future.
  • 25:26 - 25:28
    You don't want to depend, or, excuse me, you
  • 25:28 - 25:33
    want to depend on abstractions, ot concretions.
    Depend on
  • 25:33 - 25:36
    the duck type, not the concrete type. In our
  • 25:36 - 25:39
    case, depend on the concept, the concept of
    an
  • 25:39 - 25:44
    option. Not on the concrete types that implement
    that
  • 25:44 - 25:47
    abstraction.
  • 25:47 - 25:50
    In our case, option is the duck type. This
  • 25:50 - 25:52
    is the abstraction that I missed earlier,
    when I
  • 25:52 - 25:57
    just kept going down the conditional logic
    path.
  • 25:57 - 26:00
    It's really simple. It has a valid? method
    and
  • 26:00 - 26:07
    a value method. String option, integer option,
    and boolean
  • 26:07 - 26:11
    option, those are the concrete implementation
    of the option
  • 26:11 - 26:15
    abstraction. All they need is a valid? and
    a
  • 26:15 - 26:19
    value method, and a consistent method of construction,
    and
  • 26:19 - 26:25
    I can depend on the abstraction, not on the
  • 26:25 - 26:26
    concretions.
  • 26:26 - 26:32
    So, how do I do that? I could go
  • 26:32 - 26:35
    down the case statement road again and check
    the
  • 26:35 - 26:38
    option type, instantiating the correct type
    of the option
  • 26:38 - 26:41
    based upon the symbol. But I'm not gonna do
  • 26:41 - 26:44
    that, cause that would tie CommandLineClass
    to those concrete
  • 26:44 - 26:47
    types, which is what we're trying to avoid.
  • 26:47 - 26:50
    That creates a hard dependency between CommandLineOptions
    class and
  • 26:50 - 26:55
    those various classes. Instead, I'm gonna
    use the dynamic
  • 26:55 - 26:58
    capabilities of Ruby to instantiate those
    objects for us
  • 26:58 - 27:01
    using naming conventions. For string, we're
    going to have
  • 27:01 - 27:06
    a string option. For booleans, boolean option.
    Et cetera.
  • 27:06 - 27:09
    I could do this even in many static languages.
  • 27:09 - 27:13
    So this isn't something that's specific to
    Ruby. And
  • 27:13 - 27:15
    this is a very. This very simple change takes
  • 27:15 - 27:19
    out CommandLineOption class from depending
    on those concrete implementations
  • 27:19 - 27:23
    and flips it to depending on the abstraction.
  • 27:23 - 27:28
    This is dependency inversion from the Solid
    Principles, in
  • 27:28 - 27:33
    practice. Alternately, some other people have
    suggested, you could
  • 27:33 - 27:36
    use a hash and map from the string, boolean,
  • 27:36 - 27:39
    and integer symbols to the concrete classes,
    kind of
  • 27:39 - 27:41
    like what Sandi did in her Gilded Rose Coda??
  • 27:41 - 27:44
    solution earlier.
  • 27:44 - 27:47
    That's OK. But, it is an additional thing
    that
  • 27:47 - 27:51
    I have to maintain over time. It's a reason
  • 27:51 - 27:55
    to open the CommandLineOptions and change
    it if I
  • 27:55 - 27:58
    have to add a new type of option. If
  • 27:58 - 28:00
    using the dynamic ability of Ruby bothers
    you, then
  • 28:00 - 28:03
    make a hash. Personally, I'm fine with using
    the
  • 28:03 - 28:06
    dynamic capabilities of my language.
  • 28:06 - 28:11
    So, in my case, I've inoculated CommandLineOptions
    class from
  • 28:11 - 28:14
    needing to change to support new option types.
    And
  • 28:14 - 28:17
    at this point, this class should be closed
    for
  • 28:17 - 28:21
    modification, but open for extension.
  • 28:21 - 28:23
    So, now we need to move the logic for
  • 28:23 - 28:27
    the various option types to the appropriate
    option classes.
  • 28:27 - 28:29
    I decided to make a base class of option
  • 28:29 - 28:32
    for my concrete types to inherit from, because
    the
  • 28:32 - 28:34
    manner of initialization needs to be the same
    for
  • 28:34 - 28:38
    all of them. No sense of repeating that code.
  • 28:38 - 28:41
    And the subtypes have a cohesion around the
    flag
  • 28:41 - 28:43
    attribute, and the wrong, excuse me, the flag
    and
  • 28:43 - 28:48
    the raw value properties that in the code.
  • 28:48 - 28:50
    Here's the boolean option. This one I just
    wrote
  • 28:50 - 28:53
    because the requirements are so simple. Booleans
    are always
  • 28:53 - 28:55
    valid, and they just return the raw_value
    from the
  • 28:55 - 28:57
    command line. If it's present, it's truthy.
    If it's
  • 28:57 - 29:02
    nil it's falsey. Very simple.
  • 29:02 - 29:03
    But now we need to implement string option
    and
  • 29:03 - 29:06
    integer option. And the logic for their validation
    and
  • 29:06 - 29:11
    value extraction is in the old CommandLineOptions
    class. So,
  • 29:11 - 29:14
    on the left are the original CommandLineOptions'
    valid? and
  • 29:14 - 29:17
    value methods. On the right are those new
    string
  • 29:17 - 29:19
    option and integer option classes.
  • 29:19 - 29:22
    As you can see, the process of creating the
  • 29:22 - 29:26
    option class was simply picking apart and
    disassembling the
  • 29:26 - 29:30
    old command line option class. Moving the
    logic to
  • 29:30 - 29:34
    where it belongs, using a combination of extract
    class
  • 29:34 - 29:38
    and move method refactorings, we've really
    cleaned up the
  • 29:38 - 29:40
    command option, CommandLineOptions.
  • 29:40 - 29:45
    Frankly, there's not much code left there
    anymore. So,
  • 29:45 - 29:48
    now we can replace that nasty, hard to understand
  • 29:48 - 29:53
    valid? method with this. And the large value
    method
  • 29:53 - 29:58
    with this.
  • 29:58 - 30:03
    To create the specs for the various option
    classes,
  • 30:03 - 30:06
    I moved the corresponding section from the
    CommandLineOptions spec
  • 30:06 - 30:08
    to the corresponding area for the particular
    type of
  • 30:08 - 30:12
    option, and then lightly reworked them and
    then I
  • 30:12 - 30:14
    worked them from red to green, as I went
  • 30:14 - 30:17
    through the process of extracting those classes
    and moving
  • 30:17 - 30:23
    the code to those methods.
  • 30:23 - 30:26
    We've isolated abstractions here. And how
    do we do
  • 30:26 - 30:30
    that? We separate the what from the how, like
  • 30:30 - 30:35
    we've done in CommandLineOptions. We want
    to move from
  • 30:35 - 30:39
    code that looks like this to code that looks
  • 30:39 - 30:44
    like this.
  • 30:44 - 30:48
    The original CommandLineOptions' valid? method
    contained all of the
  • 30:48 - 30:52
    how. The refactored valid? method says what
    we want
  • 30:52 - 30:55
    done for us. That's it. All of the how
  • 30:55 - 30:58
    has moved to the collaborators of our main
    class,
  • 30:58 - 31:02
    in this case, StringOption, boolean option,
    and IntegerOption.
  • 31:02 - 31:06
    We want to move from that looks like this
  • 31:06 - 31:09
    to code that looks like this. Move the nitty
  • 31:09 - 31:12
    gritty details of your code out to the leaves
  • 31:12 - 31:16
    of your system. And let the center be a
  • 31:16 - 31:17
    coordinator.
  • 31:17 - 31:21
    So, when we're done with this, this is what
  • 31:21 - 31:26
    our CommandLineOptions class looks like. These
    are our public
  • 31:26 - 31:30
    methods. It provides a very small surface.
    And it
  • 31:30 - 31:33
    fulfills the use case. And these are the private
  • 31:33 - 31:36
    implementation cruft. It's necessary, but
    no one really needs
  • 31:36 - 31:37
    to go poking around in here, and I've made
  • 31:37 - 31:41
    it obvious by declaring these methods private.
  • 31:41 - 31:44
    They're for me. Not for you.
  • 31:44 - 31:47
    So in the end, the sum total of the
  • 31:47 - 31:50
    implementation of the public interface, and
    it's all delegated.
  • 31:50 - 31:54
    All delegated.
  • 31:54 - 31:56
    So in the process of making the specs pass,
  • 31:56 - 31:59
    I commented out that dreamed up code as I
  • 31:59 - 32:01
    went through the process, and then one by
    one
  • 32:01 - 32:04
    I wrote the examples and uncommented the code
    and
  • 32:04 - 32:09
    made them pass, working from red to green.
  • 32:09 - 32:16
    Then, because nothing is ever really done,
    my buddy
  • 32:17 - 32:21
    says, hey. Any chance you could add the ability
  • 32:21 - 32:23
    for me to pass an array of values for
  • 32:23 - 32:24
    an option?
  • 32:24 - 32:28
    So, to implement this new requirement, I only
    need
  • 32:28 - 32:32
    the new array option class. So I write a
  • 32:32 - 32:38
    spec example. Make it fail. Then create the
    ArrayOption
  • 32:38 - 32:43
    class, and I'm done. And this particular example,
    my
  • 32:43 - 32:49
    OptionClass is inheriting from the OptionWithContent
    superclass. And, cause
  • 32:49 - 32:51
    I actually went through this and realized
    that strings,
  • 32:51 - 32:54
    integers, and arrays all have content, so
    I abstracted
  • 32:54 - 32:56
    that superclass and, in this case, all I have
  • 32:56 - 32:59
    to do is write the value method of that
  • 32:59 - 33:03
    particular type and I'm done.
  • 33:03 - 33:07
    And it works.
  • 33:07 - 33:13
    So, we now have a CommandLineOption class
    that's closed
  • 33:13 - 33:18
    for modification, but open for extension.
    I could all
  • 33:18 - 33:22
    float types, decimal types, other types of
    options, and
  • 33:22 - 33:24
    I don't have to go back and touch that
  • 33:24 - 33:26
    class again.
  • 33:26 - 33:29
    We have small, easy to understand option classes
    that
  • 33:29 - 33:36
    have a single responsibility. Oops, excuse
    me.
  • 33:44 - 33:47
    We can. So, we have a easy to understand
  • 33:47 - 33:50
    option classes that have a single responsibility,
    and easy
  • 33:50 - 33:54
    to compose together with that CommandLineOption
    class. And we
  • 33:54 - 33:56
    can simply create new option types and have
    them
  • 33:56 - 34:01
    instantiated by convention.
  • 34:01 - 34:04
    My name is Mark Menard. My company's Enable
    Labs.
  • 34:04 - 34:07
    We do full lifecycle business productivity
    and sass app
  • 34:07 - 34:10
    development, from napkin to production, as
    I say. And,
  • 34:10 - 34:12
    I'm gonna be around the conference, so let's
    get
  • 34:12 - 34:13
    together and talk about some code.
  • 34:13 - 34:15
    And we can do some questions.
Title:
RailsConf 2014 - Writing Small Code by Mark Menard
Description:

more » « less
Duration:
34:39

English subtitles

Revisions