 I want to talk to you a little bit about confident code. I've given this talk a few times, and I used to introduce it as being about code construction. But I realized something recently, this talk is fundamentally about joy. It's also fundamentally about actually advancing slides. So when I first got into Ruby, I got into it because Ruby is designed to make programmers happy, right? And you get into it, and you write something like this. And it's just the simplest, this is the most expressive way I think I've ever seen in any programming language to say do something three times. And with a possible exception of small talk. So we start out programming with these bold expressive statements. And then our code meets the real world. And it starts looking more like this. And it starts getting cluttered up with conditionals and exception handling and special cases. And it turns into something that I think of as timid code. Timid code is code that lives in fear of its environment. It's uncertain, it's constantly second guessing itself. It's filled with digressions and provisos. As a result of this, it's constantly mixing together input handling and business logic and error handling. And the net result of this is that there's this extra cognitive burden placed upon the reader of the code. And really is that timid code is kind of like a poorly told story. I mean, code always tells a story or it tries to. But timid code is like when somebody tells you a story and they constantly pause and say um and uh, and I apologize, I know I'll do that in this talk. I'm still working on it. But you know, one of these stories is filled with digressions and they're constantly going back and saying, no wait, I forgot to tell you part of the story. And code can read like this. By contrast, confident code is code that is sure of itself. It's a style of method construction that tells a story well. It says exactly what it wants to do. And most importantly, it has a consistent narrative structure. So before we go any further, here's the code that I'm going to be using for this talk. You can check it out if you want to. It is a sort of a trivial wrapper for the very important Unix utility, Kause, which if you're not familiar with it, you can give it some text and it'll produce an ASCII art animal for you. Which is just a little speech bubble. And you can give it various options so you can make the eyes bigger and stuff like that. And you can play with this while I'm talking if you want. So here's a very simple example of using kause.rb. So if you instantiate a cow object and then you can call dot say on it with whatever you want the cow to say. And by default it produces the ASCII art animal as the return value. So this is the primary method in kause. It's the same method. You can all read this, right? What I actually want you to look at here is not the code specifically but the layout, the flow of it and the shape of it. And in order to help you do that, I have annotated it in terms of four parts of a method. So you've got parts that are dealing with gathering input, parts that are dealing with the work of the method. You've got the parts that are dealing with returning results and then stuff that's dealing with handling errors. I think you can break most methods down into these four parts. And what I want to propose to you is a style of method construction that takes these four parts and goes through them in this order. Gathering input, doing the work, the business logic of the method, delivering results, and then if necessary handling any failures that cropped up along the way. So let's start with input gathering. To make confident code, we need to be sure of our inputs. When you're talking about gathering input in Ruby, you have to talk about duct typing. True duct typing is a very confident style. Although sometimes I see code that masquerades as duct type but really it's doing these not really duct type things. Like if you're checking is this object a string, that's not duct typing. If you're asking the duct, can you quack before you tell it to say something? That's not duct typing. Duct typing treats the object like a duct and assumes that if it is not, it will complain. The opposite of duct typing is type casing. It's switching on the type of an object and there's actually a name for this as a code smell. It's the switch smell and you see this in Ruby code in the form of case statements on the type of an object. You see it in terms of checking for method existence. That's checking the type of an object and you see it in the form most often of nil checks. Nil class is a type too. If you're checking if an object is nil, you are doing a type check. I want to go through three strategies for dealing with uncertainty and input to make your code a little bit more confident. First of all, if you look in the Ruby standard library, you will see that it uses the Ruby's standard coercions liberally. You see 2s, 2i, 2a, 2sim. You see these all over the place and this works out really virtually because you have things like the open method. I can create a path name object which is not a string. It's a special object for holding path names but I can pass it to open and it opens the file. It just works because open calls 2s on its argument before trying to open it as a file. I recommend using these liberally in your code. If you know you need a string, ask for a string. If you know you need an array, ask for an array. A great way to ask for an array is to use Ruby's built-in array method. I think of this as the arrayification operator. It's not really an operator but I think of it that way because you can put anything into it and it will give you a sensible array in return. If you put an array in, it will give you the same array back. If you put a singular object in, it will give you that same object back wrapped in an array. If you give it nil, it will give you an empty array. You'll always get an array out and you can be very confident of that. Here's an example of using an array. We're passing a message into our Cal State method and that message might be a number of things. It might be a single string. It might be an array of strings. In pathological cases, it might even be nil. We have this case statement to deal with that uncertainty. If we replace that case statement with the arrayification operator, it just goes away. It just works. It's a one-liner. That's a much more confident line of code. Sometimes we can't rely on their being a coercion method which suits our purpose. We need to turn an object into something that it is not. There's no consistent 2i or 2s that gives us the coercion we want. This is a case where the decorator pattern can really come in handy where you just sort of glue a few extra features to an object, basically. Here is a candidate for some decorator code. We have this option to the same method, the out option, which is where we want to put the ASCII art. It could be the return value if it's not specified. It could be a file we want to write it to. It could be anything that responds to the insertion operator. We have this case statement. This case statement here, all it's there for debugging purposes, is we want to be able to report where we wrote the output. In order to do that one line of logging, we have to do this big case statement because there's no consistent way of describing the output type. Let's encapsulate that. Let's pull that into a method called cal sync. What this will do is it'll pull out that type case. Sometimes you can't get rid of type casing completely, but you can at least isolate it out. This is going to pull it out. The out option is a file, then just pass it through. That's exactly what we expect to deal with. If it's nil, then we'll instantiate something called a null sync. We'll look at that in a second. If it's anything else, we'll instantiate a decorator called the generic sync. How are those implemented? Here's the implementation of both of those. Null sync is just a really basic object which supports a path method and it supports the insertion operator, which does nothing. Generic sync uses Ruby's symbol delegator class to create a really simple decorator which will pass every method onto its underlying object except it defines a custom path method so that we can always depend on that path method being there. Now, we replace that line of code with this call to cal sync and what we have now is we can confidently say dot path on whatever comes out of that. Whatever comes out of that, whatever it is, we know that it'll have a dot path method and now we have this much more expressive and confident line of code where we can easily log where we put the output. Sometimes you have values coming in that you just can't do anything with. They're not a duck, they're not even a bird. Eventually they will probably cause an error. They'll probably cause some no method error to be propagated out somewhere down the line but possibly not without doing some damage first and they might not be clear where that error originated. So to avoid this case, your code needs to be assertive and it needs to be assertive about what inputs it will accept at the edges of your interface. What we're talking about here is preconditions. Preconditions are part of the Design by Contract methodology of software development but you don't need a big Design by Contract framework to do this. You don't even need an Assert method. A simple precondition might look like this and I apologize for the people that are looking at this screen. It seems to be stretching things out but this precondition here, what it says is we have an option called Calfile. Calfile is basically a template for the ASCII art that's produced and we're going to verify that if it's specified it is not just white space. We can't deal with a file name that's pure white space. Kind of a contrived example but we raise an exception right at the top of the method before letting that go any further into the method. Now we don't have to deal with that possibility any further into the method. And it's self-documenting because if you're reading through the code you see this is the first thing you see. You see this method will not accept a blank Calfile. If we want we can clean that up a little bit. We can write an Assert method which makes that statement a little bit more concise. Another way of dealing with values we don't know how to work with is to simply ignore them. And the way to do this is to use a Guard clause. Guard clause is kind of like a precondition except it doesn't raise an exception. So it's just a clause that goes at the very beginning of your method and it short circuits the method. If it sees that it's got an input that it can't deal with. So that message might come in as nil and there's nothing we can reasonably do with a nil message. We're not going to create any ASCII art for that. So we just have this return empty spring at the very top of the method if that happens. And the nice thing about this is that now we don't have to deal with that special case anywhere further down the method. We don't have to think about the possibility that message might be nil. So we're trying to avoid these special cases but sometimes you can't avoid having special cases in your code. Sometimes you need those special cases. And there's a pattern in object-oriented programming for this case and it's called, not surprisingly, the special case pattern. And the idea is if you have a special case that you have to handle, represent that case as an object itself. So here's some candidate code for applying a special case. After we call the CalSE sub-process, we have to check the exit status of the process to make sure that it didn't fail. And so we've got this code that checks for that. But there's kind of a complication. In certain cases the exit status might be nil. There might not be an exit status object. We're expecting to find a process status object there which responds to .exit status. But it might be nil. And so in this code you can see two different ways of dealing with that lack of confidence. The first way is and-and. So we say status.exit status.exit status to check that status is not nil first. The second way we do it is we use try which is inactive support and it's basically just syntax sugar for that and-and-and. But it doesn't really resolve the fundamental issue which is that we're not confident about what that status-that status variable contains. What if instead, before we-before we use that status variable, we checked to see if it was nil and if-if so we replaced it with a quick little special case object. And-and for this I'm just using OpenStruck to create a little ad hoc object and the only thing it responds to is exit status. And that's fine because that's all we need. That's the only method we care about from this object. And so if it's not there, we replace it with this ad hoc special case object and now the code just works. It doesn't have to check that status variable before it calls anything on it. Another way of dealing with this, um, these special cases is something called the null object pattern. So this is based on the observation that the special case more often than not is nil and-and more often than not the way we want to handle the special case of nil is by doing nothing. So a null object is an object that basically exists to do nothing. Here's a very basic implementation of a null object. We implement method missing to do nothing to, you know, so it'll take any-it'll take any message, any method, and it'll do nothing and then return itself. And we're also gonna-we're also implementing the nil predicate to say this object is kind of like nil. And, uh, we're also, we also make a little helper method called maybe which just helps us construct these null objects and it says it basically replaces nils with null objects. Passes other objects through, but if an object is a nil, it gives you a null object back. So we can use, um, we can use this to-to make our code a little bit more confident. By the way, this is something, um, this particular variety of null object is known as a black hole object because it returns self, you can nullify arbitrary, uh, change of execution. Here's an if statement where we say if the user has specified, the client has specified that out option, then write the output to that variable. Otherwise, don't. We can replace that with our little maybe helper which-which will construct a null object in the-in the case that that's nil. And now we can simply con-we can confidently write output without any kind of conditional there because we know that even if it's nil, it'll be replaced by a null object and null object will just take that input and do nothing with it. You might have noticed the trend at this point. Um, uh, going through a lot of strategies to get rid of nils and, um, I really think nil is kind of overused in RubyCoded. It represents so many things. It can mean there was an error. It can mean that there's just missing data. It can be a flag for default behavior. It can-it's the default value for uninitialized instance variables. It's even the default return value for things like ifs and unlesss if you hit the, um, the unhandled case. So, um, and-and as a result, I-I find that nil checks are the most common form of timid code. Uh, so I try to eliminate nils wherever I can find them. There's a few strategies you can-you can use to do that. One of them is to use the fetch method. Uh, how many people are familiar with fetch? A few people. All right. So, fetch is on pretty much all the standard enumerables, all the standards, um, uh, collections in Ruby. And what you do is you give it a key, and it acts like the, uh, the square rack. It's like the subscript except you give it a key, and if that key is not there, it will execute whatever fallback action you specified in the block, which could either-which could return a default value, uh, or it could raise an exception So, um, you can use fetch as an insertion. You can say, um, fetch instead of using square brackets, and by default, if you don't specify a block, it'll raise an exception. You don't, uh, if that key is not there. If the required key, um, if-if you have a key where you want to be a little more-more explicit about what went wrong, you can pass a, uh, you can raise an explicit exception in the block there. You can use it for defaulting. So, um, instead of using the OR operator, you can specify default value in the, uh, in the block to fetch. Um, how many people have-how many people deal with this kind of error on a daily basis? No method-no method error on nil class. Um, where-and-and how often do you find yourself thinking, where did that nil even come from? There are so many places that nil could have come from. Can anyone tell me what's different about this error message? So, here we're replacing-instead, we're replacing the nil case, the, uh, the unspecified case with an explicit symbol called no-logger-set. What's different? What's better about this error message? You know exactly where it came from. It came from, exactly. If you see this, no-you see that the-it's-the no-method error is now on the symbol no-logger-set, and you can grep for that. You can search the code base, and you can figure out exactly where you failed to specify a value that the code was expecting. Another thing you can do, um, is you can use a null object for defaults. This is great for loggers. Um, if you have optional logging, you can just make a null object your default logger, and then you can just make your log statements all throughout the code, and they'll just do nothing if-if no-logger is specified. You don't have to check for it. So that was all, um, the first, uh, step, and our-our four steps of a narrative method. The second step is performing work. And there are a couple of styles of performing the business logic of a method that I find lend themselves to confident coding. So, um, one of them is chaining. Chaining is a very Ruby-ish style. We chain a bunch of methods together, uh, to get a result. And chaining is great because if you have- if you're not sure if that initial object exists, if you're not sure if it might be a nil, you can just wrap that initial object in a maybe, you know, get your null object in there, and if it's not there, then the whole rest of that method is just a null op. Um, the whole rest of the method gets-gets niled out. So you don't have to do checks at every step along the line. Another confident style is the iterative style of getting work done. And if you've done any work with jQuery, you're probably familiar with this. In jQuery, you almost never work with singular objects. You always work with collections. Sometimes they're a collection of one- they're a collection of one object. Sometimes they're a collection of no objects. But you always work on collections. The thing about working with singular objects is that they're implicitly, uh, one or error. Either there's something there or there's a problem. The thing about working with- with collections is that they're implicitly zero or more. There's no error case in there. It's just zero or more. And nothing happens. Uh, Cal State uses this style. So messages might be an array of messages. So it just converts- it just goes ahead and at the very- very start, uh, converts message to an array if it's not already. And then it iterates through that array. And if there is nothing in the array that's empty, then nothing happens. There's no exception for that case. Alright, so step three. That was- that was- that was gathering input, and now we're on, um, delivering results. Don't have a lot to say about this point, except, um, be kind to your callers. Don't make them deal with nil if they were expecting some other kind of value to come out of your- out of your method. Consider, um, returning, you know, if you can't return the object they were expecting, consider returning some kind of special case object instead. Or if there's no special case object that makes sense, raise an exception. But don't make them deal- don't make them have to check for the nil case. Alright, so that's- that's our- our first three steps. Our final step is handling failures. And, um, the first thing I'll tell you about this is try to put the happy path first. You know, if you're reading through the code, you want to see what the code is supposed to do. You don't want to- you don't want to get distracted by all kinds of- of tangents about, oh, and this might happen here. Oh, and this might happen here. What we want to do is we want to try to put that- that failure handling at the end, or, um, isolate it out to separate methods. So here's, um, here's the digression in the code. Uh, we looked at this code a little bit before. Uh, we've- we're- we're checking in the middle- right after we do that P-Open call to- to actually run the CALSAVE process, we're checking the exit status. And, um, and- and this is an interruption in the middle of understanding if you're reading through the code. This is an interruption in the process. We've got to think about, um, oh, sometimes the exit status might be bad. Um, and- and what we can do with- with code like this is we can extract it out to something called a bouncer method. And a bouncer method's job is to either raise an exception or do nothing. That's a toll job. And here's a b- here's- so here's a bouncer method that yields to its block, and then it checks that one condition, that exit status condition, and either raises an exception or it doesn't. And that's- it's only job. And then we can wrap that around our CALSAVE P-Open and it becomes much more expressive, um, because now we're not interrupted by that- by that glaring raise right in the middle of the, uh, of the narrative of the code. Here's another interruption. A begin, rescue, and wrapped around the- the, um, reading the reading of the results back from the process. And, uh, it turns out in certain cases we might get an E-pipe error. And so we have to handle that. And you're reading through the code and you get to a block like this and it completely throws you off. I mean, you- you- you're- you were thinking about making AsciiArt animals, now you're thinking about E-pipe errors. And by the time you get to the end of a digression like this, you might not even remember what you were reading about in the first place. Let's extract that out again. This time we'll use something called a checked method. And a checked method is just a version of a method that you're already calling except it encapsulates, uh, failure handling for that method. So we're gonna make a checked P open and this one is going to always check for, um, E-pipe errors. Apply that to the code. Now we're not interrupting the narrative of the method to talk about E-pipe errors. So when we apply all of these refactorings, we come up with something that looks like this. We've got those- we've got those parts of the method. First we're gathering input. We've got, uh, doing the work and then we've got returning results and they're in that order. And all of the error handling has been factored out to other methods. So what can we say about this- this final product? Well, um, it does have that coherent narrative structure that I'm talking about. It has lower complexity. I'm using this in the specific computer science meaning of it has fewer paths. It is not necessarily shorter. The code that I just showed you was, um, was a little shorter, but that's because we pulled all this code out into separate methods and helpers and null objects and stuff like that. In fact, the final product, uh, result is a little bit longer. And this- this style of coding is not about reaching the- the most concise possible, um, statement of what you're doing. It's about being expressive. It's about telling the story well. And it might- that might mean that it actually comes out a little bit longer. Why do we care? What is the point of trying to write code in this style? Well, for one thing, um, you know, we said it has fewer paths and we know from research that fewer paths are associated with fewer bugs. So there's that. It is typically easier to debug. Usually methods, um, fail more quickly rather than letting, you know, bad values get deep down inside and then spring a bizarre and inscrutable exception on you. Um, it's more self-documenting. You know, there's those old saying, write your programs for humans first and then for the computer. And it results in methods that express themselves better to the reader and that might be you in six months. But I think most importantly of all, uh, the reason is joy. It's getting back to that beautiful expressiveness and that feeling that we get when we say three times do put us hello world. That feeling of just writing out exactly what we mean and, and nothing more. So that's all I've got. Thank you very much, uh, for listening and I think I have 30 seconds for a question. I showed the, the chaining, um, and chaining, of course, in Ruby is very common with the new fit well. Um, but of course, when you chain on innumerables, you use the Baying methods. The Baying methods will return nil. Do you feel that that's incorrect behavior because it then introduces a nil into the middle of the chain? Do I feel that, that using Baying methods, um, is, is incorrect or the, the fact that Baying methods return nils into the chain, um, that that's incorrect behavior. Uh, generally, unless I have a very specific reason I'm not using the, the Baying methods. Um, uh, so I, I'm usually using the, the non-destructive versions, uh, because I'm not gonna, I'm not gonna optimize until, until I have a profiler result that says I have to optimize. That answers your question. I think I'm out of time, um, but please feel free to catch me in the hallway, um, or at lunch if you have any other questions. Uh, thank you so much.