Another source of risk in software development is the combination of the likelihood that something will have to change and the effort required to make that change. I use tests to drive the design of my software, which has the nice features of building a comprehensive test suite for the intention of the code and of ensuring that I design my code in a testable way.

That risk may be low, and that's fine. Remember, though, that code has a way of sticking around far longer than you anticipate.

If you want to be able to maintain your code later, you have to be able, right now, to look beyond the mechanics of how you tell the computer what to do. Source code does that, yes, but that's not its primary purpose. The primary purpose is communicating what you intend to do to other programmers.

For design in the large, increasing testability usually means improving genericity, writing to well-defined interfaces, and removing coupling between unrelated systems. Yet if you, as a novice, have to worry on your own about design in the large, something is very wrong with your development process and you have problems beyond which tips for novices can help.

You can manage these risks in the small, too.

The risk of maintenance is twofold. First, you can write messy code with false trails and vestigial code and unclear logic and poor factoring. No language prevents this. You need to develop good taste for organizing, designing, and implementing code. Second, you can write ill-thought code which crams together clever tricks and side effects and strange idioms but serves to obscure the intent of the code.

The second happens less often than the former, despite so much of the software development culture brandishing pitchforks, torches, and languages with enforced indentation against the latter. One wonders at their priorities.

In general, less code is better than more code. I saw some code today which copied arrays around, pushed onto arrays, pulled values from a hash, and then finally inserted a reference to that array into a hash. The corresponding idiomatic Perl 5 reduced six lines of code into one line of code -- and not a cryptic one-liner, either. Yes, you have to understand references and dereferencing and autovivification to understand that line of code, but anyone dealing with this code that didn't understand references or dereferences is already in trouble.

In general, you can successfully replace structural code with functional code, with no loss of clarity. While understanding the mechanics of what Perl should do seems more important to novice programmers, the intent of why that behavior is necessary is more important to maintenance.

In general, you can safely assume that the first question people will ask when maintaining a piece of code (a function, a module, a class, a program) is "What does this need to do?" Your tests can answer that with regard to details. How you write the code (and comments and inline documentation) answers that with regard to purpose and intent.

In general, there's no reason to use clever tricks when simpler code will do. There's one reason to use a clever trick, and that's when there's really no other way to accomplish what you need to accomplish and when you've abstracted away the clever trick into a well-encapsulated black box with appropriate documentation and warnings and comprehensive tests. This is less often necessary than you think. (As a quick example, Moose has obviated almost every need I've ever had for even knowing how some of these dirty tricks work in Perl 5, and I'm glad of it.)

If you want to be able to maintain your software, you have to write maintainable software. This is a subtle point, bordering on tautology, but it's a necessary point that novices all too often overlook.