This presentation was at the Japanese Perl Association Seminar #1 in Akihabara on April 21st, 2009.
It covers ideas for how to have establish good habits one by one, and strategies to get them to stick.
2. Better Programming Practices
• Jay Shirley, firstname.lastname@example.org
• On IRC (a lot), irc.perl.org as ‘jshirley’
• IT Director, National Auto Sport
• Co-Founder of www.coldhardcode.com
49. Better Programming Practices
A Word of Caution
• Everything will look like a Role
• That’s normal
• Resist the temptation, then build a role.
Best is theoretical, it is something that cannot truly be reached because you can’t ever really know. It is an impossible goal, so lets just be better.
We can build applications better, we know this because we can look at how other people do things.
Even people who do things better than others can still be better. I know this because I’ve known people with very good practices still complain about shortcomings. Whether it is lacking test coverage, QA, staging, there is usually something missing and sometimes the complaints turn to whine.
Well, we’re developers. We fix problems, right? So just fix it.
When you look backwards, there are a lot of moments where you can point out flaws. Either your own or others, but this is very important. When you do this, you can learn and try to think of ways to do things better. Better yet, look at what other people have done. This is where the Perl Community (p5 porters, CPAN) really shines, they have all these tools built for you. Let them make the mistakes, and you learn.
After acknowledging your failures, the next step is to analyze your next attempt at a problem. This is where studying other people works, but you have to get rid of the Not Invented Here attitude.
Dropping all that, accepting your own limitations and doing better will make you happy. It will make you happy because you’re not trying to pick up a mountain. You’re simply enhancing something you’ve always done. If you start jogging, you don’t run a marathon. Start running a half kilometer at a time and work your way up. Perfect your technique, tempo, strategies. This all falls into 3 concepts of what constitutes “Software Development”.
The first, very easy and very vague.
The best way to enhance your strategy is to do an initial look at design. Design is 100% about goals. Defining your goals in your design step means your implementation will be complete, and hopefully robust.
Another aspect of design is figuring out your role in the project. If you’re not a frontend guy, don’t venture into that territory. It’s hard to let go of some control, but you really can’t do it all. Design what falls in your realm, set those goals, and let other do the same.
Testing is equally important, but testing happens iteratively and as time goes on your code progresses in maturity; your tests will increase in quality and coverage. A test without a design isn’t going to do much more than tell you what you already know.
Another point is to not get fixated on code coverage., At least during iterative development phases, it is better to work on testing your API and not focus on code coverage. The reason is simple, in the beginning of a project a percentage of the code initially written will not be there at the end. This is refactoring.
Yes, you will have to refactor code.
This is why you have to accept your flaws, because your code is flawed. The first invention of any solution isn’t the right one, but that’s ok because software development doesn’t have manufacturing costs associated with it. Enjoy deleting lots of code, do it often, just make sure you use something you can commit to frequently, preferably in branches (like git or svk).
By building a solid and comprehensive API that is both simple to use, and makes sense, you’ll increase productivity and reduce bugs (less code rewriting) -- both things will make your boss much happier.
In the charged world of Ruby vs Python vs Perl, or vi vs emacs, it is nice to have something to agree on. I hope we all can agree on these principles here.
It’s very hard to say “No, you never refactor code.” or “Testing isn’t good!”
It’s also very hard to do any of this in your own projects, and even harder to make them habits. That’s really the big problem here, since all of these points have been talked to death.
A lot of the burden falls on whoever maintains the development environment. For inspiration, look at the CPAN world. When a module goes to CPAN, you have Smokers (CPAN Testers) and a variety of other tools (such as Kwalitee) that help you write better code.
These are -voluntary- tools built by others that you can use for your stuff. Email yourself statistics. Nag yourself, and you’ll want to do better. Setup a smoker, setup email reports and even kwalitee. Use custom perl critic specs. The CPAN world has better supportive tools than most software companies. At your company: change this! You can. Use CPAN as an inspiration, setup a smoker and a custom CPAN repository for your companies packages.
You are the master of your own destiny, and when you establish a habit it becomes easy to drop if there isn’t an eventual reward if it is harder to maintain the habits otherwise. Having tools that help you keep them, such as Smokers and custom CPAN paths, aren’t enough.
The real eventual reward for good development practices is doing your job in less time. That means you can write more fun code, or just read RSS feeds. At the very least, you can justify a raise easier.
So, in practice, setup tools that nag you (Smokers, CPAN overlays, etc). Use iterative testing strategies to get better APIs, then ultimately write less code and do more.
This is very general, though, and each environment is different. The hardest step is really the testing, and that’s because people either fixate on coverage metrics or some other detail.
To make it easier, understand what your goal is. Testing is very different based on what you are testing. Schema classes are based on unit testing, figuring out what works and what doesn’t. What relationships need work, what is missing from your API (that is what your schema class really is)
When you test an application, like a web application, you are testing workflow. The difference here is that your unit tests prove the underlying things work. Workflow tests make sure things like email verification works.
Tests are not to prove it works. Tests are to prove it doesn’t break, and that your API does what it should.
The difference is in coverage, for the first step cover your API. After that work on complete code coverage
Tests do not require more time to finish your project, unless you release really buggy software. I’ll buy a beer to anybody who can prove otherwise, unless you work for the Internet Explorer team.
The next phase of testing after API testing and then basic coverage tests is going to be your acceptance tests.
I like to think of these mostly as workflow tests make sure things like email verification works, so people can sign up.
At this step, it is about aligning to specifications, the design goals and end-user acceptance (also good to count number of “clicks” and test for that)
If the test is hard to write, it is hard for the user to use. Except you only have to write the test once.
So, testing is really about incrementing the scope of what you look at. And first focusing on the API. The API is what you design, so testing and design go hand in hand.
The first step of design is a rough idea of what you are after. Think of clay modeling, you don’t start with a finalized product, you shape it from rough forms. Software is, fortunately, nothing like that -- we can throw away and replace without looking at the full form.
So, think about what you want. Come up with a nice API for it, and then start filling in the methods will practical code. If a method sucks, get rid of it. Your tests will help you while you flesh this out to figure out what you want. Something I usually do is write out an “Ideal syntax sketch” that just pretends all my API functions work. Often times this turns into my first API test.
Now, after you get a rough idea of how your API should look you can think about backend implementations. This is far too similar to computer science courses where you code the methods giving the example, for this, I’m sorry. But it is a good way to do things.
Fortunately, you can cheat. This is where you find CPAN modules that do the work, or some other algorithm on Google. CPAN is better, because you can steal the tests, too.
So when you have your methods built in, your tests cover the API and you can handle (via unit tests) all the individual points of a project it is time to put everything together and build a real application.
In terms of cooking, lower the heat and start simmering. This is the boring work, everything is done and you’re assembling it. Hopefully, every project gets more boring since you can rely on code reuse from previous projects. By having everything in tested methods, you’ll know what things can and cannot do.
Also, if a method needs to be changed you can do that. With the API tests, nothing will break, unless your test is broken. It’s fluid, easily changed and very, very robust.
The other thing, is that a lot of computer science ideas come from an age where desktops were the real applications, and web applications were simply scripts.
I’m guessing that all but a few here are working on web applications that rival very large desktop applications.
Good thing, because often times we don’t know how good we have it. We control everything. We know what hardware, and however much we want to complain about how certain browsers make our lives difficult, imagine trying to support a desktop application on unknown hardware, with a lot of other variables. I’ve done it. It’s terrible.
Web applications are easier, even considering how hard Microsoft makes it. So, stop thinking about software in terms of a desktop application. We control everything, and even Microsoft provides free virtual PC images of all versions of IE.
Release early, release often, don’t fear change because you have the tests to make it safe.
So, the key to a solid application is to remember your environment, don’t overstep your goals, test as you go and finally, use the tools available. Definitely smoke servers and sandbox development environments. These tools will help you so much.
And, please, don’t reinvent wheels. CPAN has most things out there, and the hardest part is finding out the right module, since there are usually 5. Getting involved in the community helps, but looking at the unit tests of the modules is a really good litmus test for how good a module is, and how attentive an author is to the packages.
To move into Perl specific areas, lets go over what makes better Perl. Applying my previous concepts, a bit part of that is looking at what is out on CPAN now and making the best usage of it.
For now, lets break this into two categories. The first is testing.
A lot of times you see code that is highly repetitive, like fetching parameters out of a request or something. Even if this code is simple, if you find yourself typing the same chain of commands it isn’t very good.
So, simplify it. Store a reference. You can spend the couple of bytes for the scalar reference to the hash. It saves you typing, and cleans up code.
Just because your creating shorthand syntax, doesn’t mean it should turn into Golf. It’s the exact opposite, really. You’re typing less by creating more variables and then saving yourself a lot of trouble later.
Don’t use stupid obscure code like this. Even if you can write it, you shouldn’t.
That code example would die with strict turned on, as it should. Always code with warnings and strict turned on. If you don’t, you don’t know what your code is doing. People seem to have forgotten that back when C was the highest level language, you had to initialize all your variables or you got random data in them.
Don’t introduce entropy into your programs, use strict to keep things sane.
Then, if you get another source of input, you only have to change one type. This is a contrived example, but I’ve had similar things come up in practice. Being able to normalize your data into a very easy to query structure will save you in the long run. The key there is easy to query, you don’t have to obfuscate things.
And for testing, use Test::Class if you don’t already have a testing system you know and love. Ovid has written a lot on using Test::Class at modernperlbooks.com, it is easy to follow and very very useful.
I completed 3 different projects, with the intention of using Test::Class and better testing policies. I failed miserably up until the end of the final project.
This involved me talking to people at length, and finally figuring out the “right way”. The big thing is that you do have to change the way you write your code slightly, but more importantly really change the way you think about your tests. They’re not encapsulated as tiny bite sized morsels, because then you end up with no good way to share your code between tests. Test::Class helps this, by applying some structure and a development pattern.
It takes a few times to learn how to do it right. Ovid and Chromatic probably forgot that, but it’s ok to fail -- just take a moment after you fail to figure out why.
With the learning curve on doing these tests right, it may not be obvious why you should use it. There are a lot of benefits, and it really is worth it.
You won’t have the problem of copy and pasting code from one .t file to another.
Test packages make more sense, you can mix and match them. You can extend them, and override them. Your tests will come together quicker, better and generally be friendlier.
Base classes are also somewhat related to Test::Class. You save your test data in an upstream class. Base classes are a very simple concept, and Perl supports multiple inheritance (in that a package can have two or more parent classes).
So, you can easily get away with writing less code and doing more by using base classes. It does take different ways of thinking about the code you write though.
Building base classes is, in some ways, a puzzle game. If you like this sort of thing, you’ll have fun. If not, it is yak shaving. Either way, it is better to do it. One thing is really thinking about the real differences.
Base classes are great, but it will take a bit of practice to get used to building good classes. In my RESTful Catalyst applications, I have many packages that consist of nothing more than configuration blocks and an inheritance line.
The base class is very complex, works with configuration, but it covers all the use cases I have and to use it I just inherit from it, and muck with config. It is very, very fast to develop with. I also can get away with rigorous testing of just the base class and feel confident it works.
When I introduce new functionality, I start first with my existing tests and work from there, since the new stuff tends to be very complicated it is better to have a scratch pad.
I’m a big fan of Moose, but this isn’t a Moose talk. This is about writing better code, and I believe that Moose does that (and more).
Just as a simple introduction, Moose is syntactic sugar over Class::MOP. Class::MOP is an API into Perl’s OO capabilities.
Effectively, it is just a layer on top of what Perl gives you that does fantastic things to your code. I’m not going to go into all the fantastic things, because then it would be a Moose talk.
Instead, I’m going to talk about what you need to write better Perl code. A big problem is that if you think of Moose (or any OO-API package that is on CPAN) as nothing more than accessor generators and mutators, you’re doing it wrong.
Moose handles much more. Introspection, reflection, method modifiers, mutable classes, immutable classes. It has an API into the meta definitions.
But that’s a lot, and you may not need it. If you don’t, there is a minimal set of features that is called ‘Mouse’. You can use that, and speed things up and still write better code.
One thing that Moose makes really easy is configuration. This is done through the idea of ‘Roles’. Roles are a very complex subject that requires a Moose talk, and this isn’t a Moose talk, so I’m just going to say that Roles are a way of mixing in functionality. The main difference can be thought of as “A class IS A” and “A role DOES”. It’s an action.
Configuration is an action, and there is a fantastic role on CPAN that makes it all happen.
I configure most things in my application, and one thing is that I tend to have config files in different locations. Sometimes I like to override things by having a “appname_local” configuration file that gets loaded if it exists, but that I instruct git or svn to ignore.
This lets me do that, by just populating a list of locations to look for a file. While it doesn’t merge multiple files (which would be awesome), it is the next best thing right now.
So here you can have multiple paths for configuration, and then they set the accessors you have generated.
So go read up on the pod for MooseX::SimpleConfig and start using it, it’s on CPAN.