Confident Code
avdi.org
avdi.org
a) represent about 15% of the actual semantic content of the presentation; and
b) are shaped by the consideration that there's a limit to how much code you can put on a slide and have it still be readable.
A lot of this material has been covered in more detail on my blog (http://avdi.org/devblog) For instance, I wrote a whole article about Maybe, NullObject, and the limits of the ability to make objects falsy here: http://avdi.org/devblog/2011/05/30/null-objects-and-falsines...
Although you say that there is a lot of content missing, I thought your slides conveyed a lot of useful information and actually made me feel like I got what your presentation was about. Well done!
http://confreaks.net/videos/614-cascadiaruby2011-confident-c...
a) Integrating with other codebases that don't use Maybe will result in code with an inconsistent style, and
b) If Maybe (or something like it) catches on enough, you'll hit collisions where two projects or libraries have different ideas of what Maybe means. (Maybe one uses NilClass, the other the custom NullClass, for example.)
(The PHP community has taken this to the extreme, with developers getting burnt over and over again with differing "standard" components using the same set of names.)
Another way to get the behavior of the described Maybe method is to enforce a coding style where functions require a default return value to be passed in - this lets it be handled in a precise manner and surfaces most problems quickly, without butchering fundamental assumptions of the language.
The real difficulty comes in when you want to enforce a branch for functions that are "rarely null" instead of having them blow up much later when you inevitably forget to handle the null case. A lot of the time the only immediate way to avoid this class of error is to use algorithms that will never stumble over a null(which tends to make them a lot slower) - if you have macros or richer type systems, other things are possible of course.
obj.nil? # => true
!!obj # => trueYou can override ! (see http://www.rubyinside.com/rubys-unary-operators-and-how-to-r... from earlier this week) so !!obj can evaluate to false. But there is still this to solve:
ruby-1.9.3-p0 :008 > obj ? true : false
=> true
ruby-1.9.3-p0 :009 >
If only there was a #to_bool to override...(For the record: yuck).
I'd also like just letting nils be nils :) but good presentation.
module Foo
class Bar
end
end
Bar is now only accessible as Foo::Bar. You could have a Baz::Bar class that is entirely separate from this Bar.That's where the confidence comes in; you can move forward knowing that your assumptions will hereafter always apply.
Upvote for NullObject pattern -- by far my favorite pattern. Unlike most 'Design Patterns', this is a useful and non-obvious idea no matter what language you work with.
Also, I'm not really sure how a pattern like this would work in Java, although this could just be because my Java is (thankfully) going rusty.
As for Java -- http://www.cs.oberlin.edu/~jwalker/nullObjPattern/ has examples. But the case of the empty linked list is stretching it a bit. Somehow it seems to me you ought to be able to have a regular linked list where the "nothing" behaviour did not need a whole other class.
I usually explain Null Object like this:
Think of a website where you have users who might be logged in or not. A naive way to express this would be that, if the user is not logged in, your getUser() method returns null.
But then you have to keep testing for null, over and over again, before you can do anything with the user. And the user-is-null case is suspiciously similar to user-is-not-authorized-to-do-this-thing.
Solution: make a hierarchy where there's an abstract User class, concretized by NonLoggedInUser and LoggedInUser. The NonLoggedInUser returns false for all isAuthorizedTo().
Now you always have a User, it's just that sometimes that User does "nothing". So you just ask if user.isAuthorizedTo('doTheThing').
Fowler generalizes this into the SpecialCase pattern. http://martinfowler.com/eaaCatalog/specialCase.html
Making sure message is an array even if it's X instead. Why do that at all? Why is there an "else" in the first place? It's not like you can support every possible type, so why not say - either it's an array, or you pass a wrong type?
Why is the code for obtaining message in that function? Why is a similar code for destination there? Why is EPIPE being treated the same as normal response from the process?
How about starting with this instead:
messages = normalise_messages(message)
command = construct_command(options)
output = get_results(messages, command)
@logger.info("Wrote to #{destination_description(options[:out])}")
output
The rest is implementation details...More info on s5:
if (foo != null and foo.getSomething()) {
or even weird little conventions that you get used to, like if ("".equals(aString))
I think with some effort, you can avoid some of this sort of thing, but it's not always easy.(Or, perhaps you didn't intend to include Java as a member of the set "decent statically typed languages?" ;))
My personal favourite, which is C# at the moment, also has the "null" misdesign so you're right about that. Still, none of the duck typing issues exist (other than the null checking, which admittedly does undermine my point). Also, the IDE helps you do it right, which gives you more confidence as you write the code.
I'm now finding my code heading back to the old littered style whenever I have to update or get information from someone else's API just because I have to handle potential failure there and then.
Very frustrating, anyone got any insights on how they're dealing with it?
Array({:foo=>1}).map{|h| h[:foo]}
TypeError: Symbol as array index
[{:foo=>1}].flatten.compact.map{|h| h[:foo]}
=> [1]Array's terrible friend Integer() implicitly crashed my app in production before, so found that interesting to know; not necessarily related to your talk though. :)
Listed as a gotcha, but it only occurs on an old version of Ruby (1.8). Nowadays, it'll work like so:
Array("foo\nbar") # => ["foo\nbar"]
The modeling of: writing prose <-> writing code
reminds me of: micro air vehicles flying <-> birds flying.
Any thoughts out there on how to organizing tests to support "confident code"?
There were some other good points in there about encapsulation, but communicating to users properly trumps looking pretty to programmers.
(1) Performance. Coercing values that might have been the target type to start with is wasteful. Extracting secondary paths into separate functions is wasteful.
(2) Readability of non-primary paths. Extracting non-primary paths into separate functions might make them harder to follow. Error paths matter. They often demand greater diligence than the happy path, so sacrificing them for the sake of the happy path would be bad. The increased length of the code bothers me a lot less than that. Namespace pollution could also be a concern in larger projects. If the code is reused, great, but if it's single-use then this refactoring would be bad for maintainability.
(3) Complex recovery and variable scope. The cow example is fine, but a lot of real code has to deal with much more complex error conditions. This is not just a matter of what can fit on a slide; handling more complex errors introduces fundamentally different issues. Often you need to know how far you got in order to unwind properly, and that information is likely to be contained in local variables. In some cases this means you should nest functions more, but this also destroys the "narrative" structure plus look out for points 1 and 2. In other cases it's just not even feasible, so splitting the error path into a newly-created function means having that function parse what really should remain local variables.
The idea of preserving narrative flow is good. The conceptual framework of input, action, output and error is immensely useful. Given these caveats, though, identifying one style as "bold" and one as "timid" is just an immature appeal to emotion. It's not timid to write efficient code, or code that respects separation of concerns. It's not bold to write code that preserves its own simple structure at the expense of everything around it. This could be framed as selfish vs. cooperative code, making the opposite emotional appeal, and be just as valid. The goal is to write clear code, and this is just one way that applies in some circumstances and not others
And that's what happens when your programming language does not have a decent deployment ecosystem.
There is something to say to say about using a platform-dependent utility for a talk of an eminently portable language, but i do not think that is necessary here.