⇥ Universal Save for iOS Apps

September 29, 2011
No comments
 
⇥ Permalink

Federico Viticci, commenting on a piece in which Ted Landau argues that iOS needs a “Universal Save” for iOS Apps:

Specifically, Landau notes that the lack of a “universal save” option for documents that can be read by third-party apps (PDFs, text files, images) leads to an annoying and pretty much useless duplication of content.

[…]

For Ted and me, yes, being able to avoid file duplication and tedious exporting processes would be nice. But I do wonder how much does Apple care about such functionalities considering the underlying paradigms of iOS and the upcoming iCloud functionalities of iOS 5. For one, Apple really cares about application sandboxing: each app has its own controlled data environment and only a few items can be shared between multiple apps.

I’m firmly with Federico on this point. The average iOS user has no use for filesystems, files, and the concept of “saving.” That’s probably one of the biggest innovations that iOS brought forth; of course, it’s going to be unpopular with us power users, because, having learned to live almost symbiotically with our computers, we have the hardest time letting go and learning this new paradigm.

This is also a problem for developers. I see tons of threads on mailing lists where folks are desperately asking how they can get out of supporting some of Lion’s new features and keep their app’s old functionality the way it was.

That’s not the right way to go about things. If you want your app to feel at home in a new operating system1, you have to embrace and implement the user-interface paradigms that the OS describes. This is probably the most fundamental step towards providing users with a consistent experience instead of a hodgepodge of half-baked ideas—and one step that, as a developer, you skip at your own risk.

What’s interesting is that, in my opinion, these “limitations” that we power users are complaining so much about matter very little to regular folks, as demonstrated by the great success of apps that have adopted Lion’s features in full. As sandboxing makes its way to OS X, this is going to be particularly important, because Apple gives its desktop apps much more freedom, and the addition of freedom to the ability to develop software results in a very flammable mixture.

Consider, for example, the case of 1Password. When Agile Bits released its popular software on the App Store, sandboxing forced them to introduce some seemingly arbitrary limitations on how it back its data up to DropBox. The reaction from power users was duly indignant that such limitations should be imposed on them.

The reaction from the public-at-large? 1Password quickly reached the number-two spot on the App Store. You do the math.

  1. And make no mistake about it, Lion *is* a new operating system.

⇥ You are not “Mr. X”

September 22, 2011
9 comments
 
⇥ Permalink

I keep coming across code like this (lifted, verbatim, from an OSS project I’ve been working with recently):

$a = $num * $coeff + $_SANITIZED_GET['val'];

What, in the name of all that is good and just, is “a?” Or, for that matter, what are “num” and “coeff?”

Computer manufacturers, OS vendors, and programming tool developers have worked for decades to give us the ability to create identifiers of arbitrary length. Personally, I’d much rather see someReallyLongNameThatLooksALittleRidiculous than a function or variable name that won’t tell me what it does at first sight.

The same goes for data. A “1” and “0” in a database don’t tell you as much as “Yes” or “No,” which are just as localizable, indexable, and searchable. Enumerations, as far as I’m concerned, should be verbose unless there are very compelling space or performance requirements that say otherwise.

I know that there are many people who disagree with this approach, but their arguments tend to fall in the “that’s the way I’ve been taught to do it” camp. The reality is, a numeric enumeration is just as arbitrary as a string-based one, but much harder for the human eye to make sense of—and we all know that, sooner or later, all data is looked at by a human eye. There are some circumstances in which performance and space constraints come into play, but otherwise the development tools at our disposal are mature enough to handle either type as easily as the other.

Besides, using numbers to identify non-numeric value is a domain violation. It used to be a necessity twenty years ago, but the vast majority of us work in environment that are well past that point. You wouldn’t want to be called “492829,” right1?

So, if you are writing a book or teaching a course on programming, please teach your readers and students to write descriptive code. They’ll be better for it, and we’ll be grateful that you did.

  1. Yes, I know. You probably have some kind of social security number assigned to you by your government. But that’s not your name—it’s an index (that is, a numeric value) in a table that *contains* your name. Big difference.

⇥ SEO for Non-dicks

September 21, 2011
No comments
 
⇥ Permalink

SEO for Non-dicks:

So, shockingly, you should try to make your site’s content trustworthy, genuine and relevant. All of the rules have come about due to their utility in detecting those three positive metrics. Good SEO is a by-product of not being a dick on the internet.

Really good piece by Matt Gemmell. In particular, I agree, with regret, on one point: descriptive titles are important.

And I hate them, because titles are like a secret trapdoor into your content—teasing you with one meaning and then springing a completely different one once you read through. Alas, the Internet has all but killed the often understated art of writing good headings.

(Via Lex Friedman).

⇥ Generations sideways

September 20, 2011
No comments
 
⇥ Permalink

Matthew Baxter-Reynolds on the various toolsets available to mobile developers:

The fact is that if your day job involves sitting in Visual Studio writing C# applications, or building Java applications in Eclipse (which will be most of you – albeit not necessarily in Eclipse), when you fire up Apple’s Xcode and start building CocoaTouch applications in Objective-C you’re going to come face-to-face with a toolset that has not had the sort of love put into it that the open source community has put into the Java toolset and associate platforms, or that Microsoft has put into VS and .NET over the past 10 years. Apple has been caught on the back foot by the popularity of its tools and is at least one, if not two, generations behind. For example, the iOS version of Objective-C does not have garbage collection.

Leaving aside the mistake at the end (which Baxter-Reynolds corrects at the bottom of the article), I’d say that Apple’s approach to its toolset is simply different, in some cases for the better, and in some for the worse.

Cocoa seems to be built on a simple premise: that developers know how to develop, but don’t know how to design interfaces; which, I am sorry to say, is probably not far off the mark.

Thus, Apple’s assumption is that developers don’t need fifteen ways to parse an XML file, because they already share enough common knowledge to do it on their own—and if they don’t, chances are that they will be smart enough to come up with their own solution and share it with the world.

The focus of Xcode is, instead, on creating an environment where even a person who is not well versed in user-experience design can still write an app that looks and feels good. Therefore, a lot of emphasis goes into good interface metrics, good look and feel, and so on.

In other words, Apple is counting on engineers commoditizing the aspects of software development with which they are most familiar, and focuses instead on filling the gaps in other disciplines that, traditionally, trip them up.

Case in point, Objective-C has lacked a public JSON framework for a really long time1. By all accounts, this should have made OS X and iOS very unsuccessful platforms for Web service-enabled apps; instead, developers simply built (and collaborated on) several JSON frameworks of their own. On the other hand, both of Apple’s operating systems present their users with an environment in which apps use a consistent design vocabulary that is well within the grasp of even the hard-core Luddite.

There are two reasons why this is important. The first is that a lot of development is still done without the benefit of a designer’s expertise. Even medium-size teams that build apps for large audiences place much more focus on engineering than on usability and ergonomics; thus, a toolset that promotes basic design principles can be beneficial from the get-go.

Second, even where designers are involved, developers are still responsible for implementing the code that makes design possible. Therefore, a development platform that emphasizes design creates a more collaborative environment for all involved.

That said, there are some aspects in which Apple’s toolset has dragged behind its competitors. Memory management, for example, has long been one, but Apple’s approach to solving the problem of managed memory has been a very deliberate one: they tried garbage collection, figured out that it didn’t quite work (particularly on iOS, where resources are relatively scarce), and moved to a different solution (ARC) that somehow manages to create a managed-memory environment with less overhead than its unmanaged predecessor.

All in all, my personal experience has been that every development environment has its flaws and benefits. Even Xcode’s focus on design doesn’t prevent the creation of plenty of really horrible apps. Still, that’s a far cry from saying that it’s generations behind—I’d rather say it’s generations sideways.

(Via Daring Fireball).

  1. Though that is getting resolved soon.

⇥ Right conclusion, wrong reasons

September 19, 2011
8 comments
 
⇥ Permalink

ORM is an anti-pattern:

The reason I call ORM an anti-pattern is because it matches the two criteria the author of AntiPatterns used to distinguish anti-patterns from mere bad habits, specifically:

  1. It initially appears to be beneficial, but in the long term has more bad consequences than good ones

  2. An alternative solution exists that is proven and repeatable

It is the first characteristic that has led to ORM’s maddening (to me) popularity: it seems like a good idea at first, and by the time the problems become apparent, it’s too late to switch away.

I agree that ORMs are, in general, to be treated with much suspicion. Their main shortcoming is that they are a square-peg solution for a round-hole problem: attempting to map a data model to another inevitably leads to conflicts and issues, or there wouldn’t be a need for the two different data models in the first place.

The real problem with this blog post, however, is that it uses an incomplete definition of anti-pattern (there’s Wikipedia for you). In his original article on anti-patterns, Andrew Koenig1 speaks specifically of anti-patterns as juxtaposing to patterns; like ying and yang, you can’t have anti-patterns without having patterns that they can be related to.

The significance of this is that an anti-pattern is not just something that is “bad on its own.” More often than not, it is more like “a pattern that has gone bad”—the right concept being applied under the wrong circumstances.

Thus, ORMs are not inherently bad. They are just inappropriate for a number of settings in which they are, admittedly, used. An ORM can be an excellent solution to scenarios in which a key-value store is not available, or in which a very simple schema is all you need. In those cases, the overhead of an ORM may be an acceptable tradeoff for the simplicity that it brings to an application.

This, in turn, brings me to another point: using an ORM does not excuse you from understanding the underlying data model and the way the ORM uses it. If you do, the ORM becomes almost inevitably an anti-pattern, because you’re resorting to magic and applying a solution without knowing the problem.

Thus, while it’s true that ORMs encourage (and mostly fail) the developer not to think in terms of SQL queries, there is no way to use an ORM well without understanding relational data mapping. Or, put another way, the ORM doesn’t replace your knowledge of SQL—it simply adds one more layer of indirection to it.

An argument could be made that ORMs do not scale well over time, because the relationship between the overhead they introduce and the complexity of the underlying data model is not linear. This, however, seems to me like a symptom of application design, rather than an inherent flaw in ORMs.

Let me give you an example; if you’re writing an application that you know you will only need to run once (for example, some sort of importer or data analysis function), an ORM can greatly simplify your job and bring immense benefits. Will you care if it doesn’t scale? No, because you’re writing a single-use application.

If, on the other hand, you’re writing a more complex application, then you need to evaluate how well an ORM will hold up over time. That’s as much an art as it is a science, but it doesn’t make ORMs inherently bad; it simply means that you need to learn to design software properly.

  1. The original article in JOOP is not available online, but it’s included in Linda Rising’s Patterns Handbook

⇥ A New and Improved Moore’s Law – Technology Review

September 15, 2011
No comments
 
⇥ Permalink

A New and Improved Moore’s Law – Technology Review:

Researchers have, for the first time, shown that the energy efficiency of computers doubles roughly every 18 months.

Dovetails nicely with something I wrote not long ago for TidBITS.

(Via Daring Fireball).

⇥ Dashes, spaces, and incompetence

September 14, 2011
8 comments
 
⇥ Permalink

Take, if you would, a look at your credit card. Unless you come from a different planet, chances are that it, like mine, has a series of digits separated by spaces. Depending on which kind of card you’re looking at, these numbers may be grouped in different ways, but, generally speaking, this is probably a valid statement for every kind of credit card in existence.

Those spaces are there for a very specific purpose: they break the visual flow of your credit card number, providing your brain with anchors that it can use to group the digits together in a way that makes them easy to process. Although the number as a whole is no easier to remember, it’s much simpler to copy and compare something like “123 457 789″ than “123457789.”

Why, then, do so many developers still insist on asking users to enter their credit card numbers “without dashes or spaces?”

The problem here is not just one of developer laziness. Sure—in the specific example of credit card numbers, all that needs to be done at the code level can be expressed with a regular expression that borders on the stupid:

$creditCardNumber = preg_replace('/\D/', '', $originalCardNumber);

In some other cases, building good heuristics that are flexible enough while providing the proper constraints can be a little trickier, but the real issue is that so many programmers don’t even try. And that, unless you’re writing your applications in assembler, is simply incompetence.

Learned helplessness

There is a beautiful concept in cognitive psychology called learned helplessness. The basic idea behind it is that people who repeatedly fail at a task will eventually blame themselves for the failure and give up trying to do it.

This, in a nutshell, describes perfectly the way I see most non-technical folk relate to computers. They’ve just been hit so many times by software that makes no sense to them that they approach every task with fear and uncertainty—instead of making users more productive (and, therefore, happier), software makes them more depressed.

Why does this happen? Because of a million little (and big) things like the credit card snafu. Every time the user encounters a scenario in which an application creates a disconnect between reality (spaces on my credit card) and its software representation (no spaces or dashes!), the sense of helplessness increases, and productivity decreases as a result.

Let me fix this for you

The good news is, this kind of problem is due entirely to developer incompetence, which makes it easy to fix—after all, there are far fewer of us than there are users, so education should be a far simpler task.

In the specific example of credit cards, the issue is usually related to the fact that the upstream credit card provider requires that numbers be passed to them without any extraneous character, and we simply transfer that constraint up to our downstream user.

However, the constraint is not transitive: it makes sense in one direction because the provider has created an interface built for engineers, who (at least in principle) should be able to deal with the resulting requirements. Our users possess no such ability, and expect an interface built for them.

From a more general perspective, solving this kind of problem is not that difficult: you simply need to look at the constraints you create from the user’s point of view. Credit cards numbers have spaces in them in real life, and that’s how users will want to input them. There. Simple as pie.

This doesn’t mean, however. that you need to take this too far in the other direction and create skeuomorphs that attempt to reproduce reality too closely. For example, making your credit card input form look like a credit card is dangerous because the various types of credit cards are different enough that it could create confusion instead of dispelling it. Besides, the user is probably capable of abstracting the underlying data model enough that such a skeuomorph would feel patronizing.

And, please, do not think that this is something that only designers should worry about. Developers are meant to solve problems, not churn out bytes. If you don’t want to be treated like a code monkey, stop acting like one.

⇥ Suggestions for a younger developer

September 13, 2011
5 comments
 
⇥ Permalink

Every now and then, I get asked by developers who are just getting started in the trade if I have any suggestions to help them out—favourite language, tips and tricks, and the like.

None of these things matter, really, but there are a few things I wish I had known when I started out that have nothing to do with the mechanics of software development.

1. Be humble

You just finished college with full marks. You know hundreds of algorithms backwards. You’ve read Don Knuth’s seminal programming series cover-to-cover (something that Professor Knuth himself would probably be disinclined to believe).

Alas, that does not a programmer make you. You’ll find out soon enough that being successful at writing software means quickly discarding options that are unlikely to lead nowhere, while zeroing in on those that will. And, more often than not, it means throwing away the book and violating every rule to get where you need to go.

2. There is no magic

Never assume that other developers are necessarily smarter than you. Remember that the people who write the tools you use have access to the same kinds of resources and techniques as you do.

Never accept the frameworks and APIs at your disposal as black boxes; understanding how they work internally will help you write better code against them.

3. Programming is a craft, not an art

Good code, like art, is many things to many people. Working code, however, is undisputedly code that performs its tasks properly and efficiently.

A good developer is like a good artisan, constantly balancing the needs of form and function, but always focused on making things work in the most practical way possible.

4. Software solves problem

Writing code is a crucial, but relatively minor, part of the software development process. You write code to solve problems, and can’t be good at one without being good at the other.

Strive to be part of the entire process that leads to the development of a system; insist on being treated not as a code monkey, but as an integral part of the overall team that contributes more than mere lines of code.

5. Code doesn’t leave sawdust

One of the best aspects of software development is the fact that it is a purely intellectual exercise. With a modicum of precaution, we can experiment knowing that our only waste is time; there is no wood to chop, pencils to sharpen, chemicals to react, or paper to crumple.

Sometimes, trying things out in practice leads to unexpectedly efficient solutions that all the planning in the world could not have anticipated. And, when it doesn’t, there’s always source control to take you back in time, ready for another try.

⇥ Maybe we should all be a little more intolerant

September 12, 2011
One comment
 
⇥ Permalink

Like most other people I know, I’ve been taught that the right way to respect other people is to tolerate them. Everyone is entitled to their opinion, beliefs, choice in religion, sexual orientation, and political ideology.

That might be true, but it’s not a productive way to lead our lives. Tolerance is but a feeble excuse that allows us to keep wallowing in our misogyny, racism, and homophobia while presenting a progressive, modern façade to the outside world.

Tolerance, when you think of it, is a mightily ugly world. You “tolerate” pests; the very dictionary definition of “to tolerate” includes words like “endure,” “put up,” and “stomach.” Is this the way you want to relate to other human beings? I certainly hope not.

More than anything else, tolerance doesn’t make us better human beings. It simply makes it possible to ignore the fact that someone around us could have a better outlook on life and close in on our own beliefs without ever needing to question them.

I grew up in a fairly conservative environment: Catholic upbringing, spent a lot of time around priests and nuns. And, despite what I keep hearing about the Catholic Church, I don’t regret one bit of it. But I also grew up in a progressive environment, in which my family and teachers always pushed to keep a critical eye on the world around me.

As a result, I have developed a somewhat eclectic system of beliefs in life. I am against organized religion, and very much pro-science1; I wholeheartedly support gay marriage, and look forward to the day when the words “who is openly gay” are going to sound as absurd as “who is openly human.” I am generally against abortion, and think that Affirmative Action is a bad idea. And so on, and so forth—many contradictions that make it difficult for me to say “I’m conservative” or “I’m liberal.”

This doesn’t, however, mean that I think that people on the other side of these (and many other) issues are stupid or evil. Wrong, maybe, but never beneath me, and always worthy of discussion and discovery. Some of my best friends and colleagues have outlooks on life that are far different from mine, and I enjoy nothing more than understanding why together with them.

I don’t expect that they will change my opinion, or that I will change theirs: dig enough into any person’s moral and ethical systems, and you’ll find that they are anchored in beliefs—which, by their very nature, resist logic. But, by understanding another’s point of view, you can learn more about them, and, most importantly, you can learn to empathize with them, transforming your society-enforced “tolerance” into respect.

This kind of intolerance—call it intolerance of tolerance—can be very beneficial to society. You can either be Richard Dawkins and proudly proclaim that the 75 percent of the world’s population that believes in some sort of divine presence is stupid, or you can ask them why and try to understand where their beliefs come from. In the first case, you’re simply peddling religion by another name; in the second, you might just enrich your life a little, and maybe the lives of others.

  1. Which is not the same as saying that I do not believe in the divine, mind you.

⇥ The easiest way to add unit tests to your application

September 8, 2011
One comment
 
⇥ Permalink

Unit testing shouldn’t be hard. In fact, given its usefulness, it should be easy and pleasant—a tool that every developer would never want to do without.

And yet, I see programmers (and, more frighteningly, systems architects) struggling with it constantly. In many cases, they are saddled with large codebases that have never seen any tests whatsoever on one hand, and the fact that a recent changes has caused their entire system to crap out on the other.

The unfortunate thing is that most “experts” will tell you that you need to drop everything you’re doing right now and spend however long it takes to build a complete testing harness for your project.

These people, in my oh-so-humble opinion, live in a fairy-tale land in which deadlines are things that happen to other people and management is made entirely of sugar cookies.

Back in the real world, stopping development for weeks while you figure out how to add unit tests to cover your entire codebase is simply something that cannot be done (at least, not if you want to keep your job), no matter what future benefits it might bring.

The good news is, adding unit testing to your existing project only takes five minutes—which is pretty much how long it takes to get a unit testing framework installed. That’s it. Move on.

A test today is better than a thousand tests tomorrow

Still here? OK, read on.

Chances are that your application “mostly works” today. If it didn’t, you’d have much bigger problems to worry about than unit tests. Adding unit tests is not going to solve any problem because you don’t have any.

That’s fine; you just need to look at testing in a different light.

Where unit testing helps is in managing change. The job of unit testing is not to determine that your application works as advertised: that’s what your QA process—of which unit testing is part—is for.

Rather, unit testing is there to help you ensure that changes you introduce in your code do not have consequences that you did not intend.

Once you make this decision, adding unit tests becomes really easy—and very productive.

Fixing bugs

A typical example is correcting a deficiency in your code. Obviously, you can’t fix a bug until you’ve reproduced it. And, when you have figured out a way to consistently trigger the bug, you’ll have to do so multiple times as you try to figure out a solution to the underlying problem.

Curiously, that exactly one of the things that a unit test does well. Instead of triggering the bug manually, you can simply write a test that does so programmatically; in so doing, not only have you added a very important test to your code—you actually made reproducing the bug (something that you will probably need to do multiple times as you fix it) much easier.

Refactoring

Another place where writing unit tests is an easy part of the development process is refactoring existing code. The worst thing that can happen here is that a change you make breaks something else, and you don’t find that out until the code is in production. That, as they say, would be bad.

Since that’s something you (a) want to avoid and, therefore, (b) you will have to track anyway, writing a unit test is a great way to automate the process. More interestingly, it’s an easy way to write a meaningful test that validates how your application works as opposed to what your code does (more about that later).

Adding new functionality

This leaves with the scenario in which you’re writing completely new code1.

Proponents of TDD and (to a certain extend) AOP will tell you that you should either write your tests before your code, or alongside it.

I know I will probably catch a lot of flak for this, but here’s my recommendation: do absolutely nothing.

Writing code is often a process of near-scientific discovery. The abject failure of the waterfall model should have taught us by now that the idea development and architecture are two completely separate phases is simply at odds with reality.

In the real world, we have to contend with imperfect specifications, unknown constraints, and the distinct possibility that, halfway through the development process, we find out out that we’re trying to solve the wrong problem.

As a result, I, personally, prefer to write code in an almost free-flowing manner, focusing on finding the best solution to the problem at hand. Throwing testing in the mix, while definitely a good idea in principle, just adds one more level of complexity to an already overly complex task in practice.

This doesn’t mean, however, that tests are out of the picture.

If you think of unit tests as a tool to manage change, in this particular scenario their job is to ensure that the change you’ve made (adding new code) works the way you mean it to. In other words, once your code is completed and working the way you think it should, you can write a test that exercises it to “solidify” it as a future constraint.

The best time to do so is when you commit your code to HEAD2. That’s because committing to head indicates specifically that the code is complete and its behaviour should therefore be invariant from that point forward.

Better yet, if you work in a team with more than one person, you can simply make writing tests part of the code review process—when you push your branch for review, whoever reviews it also write the tests. In addition to spreading the workload, this ensures that another person validates the principles under which you built the software, increasing the likelihood that they will poke holes in your programming and reduce the incidence of defects.

Writing meaningful tests

Writing tests is pointless if they don’t actually help you manage change. The question, then, is: how do you write meaningful tests?

That’s a complex topic, but the best thing you can do is to think of unit tests in relation to software design. (Or, you can grab the slides from last year’s Codeworks Tour, where my colleague Keith Casey made a wonderful presentation on this topic.)

In architectural terms, software is a kind of mapping between visible affordances (what your application can and must do) and constraints (the ways in which it must be limited so that things are done properly). Any kind of QA that you perform on your software (including unit testing) must be built around the concept of validating your code as a whole, even when you are only exercising a small portion thereof.

A good test, therefore, is not concerned with replicating all the possible sets of inputs and output of a particular piece of code; it’s only concerned with replicating those sets of inputs and outputs that can happen during its intended usage.

The difference is critical, because in the first case you’re exercising the code in isolation from its surroundings, while in the second you’re exercising it based on the role it’s meant to play in the final product.

Of course, this still means that you need to test all the realistic inputs—including edge cases and input that is purposely incorrect or malicious in nature. And that’s why it’s often better to have another person write your tests, since they will be able to look at your code in a more detached way.

What about code coverage?

It should be obvious by now that complete code coverage is not a useful goal of unit testing. Instead, it should be a consequence of a good testing strategy.

Again, the difference is substantial. If you’re trying to achieve code coverage for the sake of it, anything less than 100 percent means that you need to write more tests.

If, on the other hand, 100 percent should be the consequence of a good testing strategy, situations where it is not achieved could simply indicate that you’ve got dead code branches that need to be pruned. Writing more tests then becomes the next step in the process.

Testing is a continuous process

All these things will help you get started with unit testing, but writing good, meaningful tests remains a hard thing to do.

This doesn’t mean that you should be discouraged, or that you should give up. Just think about testing as a process of continuous improvement. An imperfect test is better than no test at all—as long as you understand the limitations of your testing strategy and strive to make it better over time.

Some offbeat reading

You’ll find articles and books on how to write good unit tests all over the place. Most of them are bad, many are well-intentioned, and very few actually explain why you should write unit tests in a particular way.

Rather than give you yet another list of those, allow me to suggest three books that are not on unit tests at all, but, instead, on the psychology of design, which is, in turn, a great tool to help you understand how to write tests that are meaningful.

  • The Design of Design is a great read from Frederick Brooks (suggested to me by Joël Perras) on the process of designing software.

  • The Design of Everyday Things by Donald Norman (one half of Nielsen Norman) is an excellent exposition on the psychology behind design.

Both these books should help you understand what to test for in relation to how a software application works. Rather than focusing on the mechanics of your code, building tests around its intended functional specs will yield much better results (and help your sanity).

Finally, you may want to read Apollo by Charles Murray and Catherine Bly Cox. This book has little in the way of technical content, but shows how the Apollo program was almost destroyed by an insistence on disconnecting testing from development, until George Mueller told engineers to suck it up and test the whole thing as one unit.

  1. Note that this doesn’t include making old code do new things–that still falls under refactoring.
  2. Or when you push to the remote if you’re using a DVCS