I mean, pairing is a fine approach when training someone up on a codebase, but it tends to be much more effortful for the guy in the driving seat, while the guy looking over your shoulder is making small comments and doing researchy lookups. This makes it less than efficient when both people are at the same knowledge level. The extra eyes looking for bugs is debatable; the bugs are more thoroughly found with tests.
Test-first TDD is even less popular. Norvig vs Jeffries was enlightening - http://devgrind.com/2007/04/25/how-to-not-solve-a-sudoku/ .
Software does have a much heavier focus on testing than it used to, to the point that in many projects, everything is implemented twice - all features have two representations, one in the form of the implementation, another in the form of tests, and often with the lines of test code outnumbering the implementation.
But other things have suffered IMHO; making code easy to test tends to over-abstract it, making it more parameterized and exposing more implementation details of high-level abstractions.
APIs are often uglier with a lot more exposed symbols to handle the parameterization, with various bits and bobs asking for interfaces that only have one concrete implementation that can only be created by a factory, and you have to learn the knack of actually instantiating the useful bits anew for each library. I've got Java squarely in mind, of course, and I'm convinced better language design can solve the problem with less harm to the software.
If the goal in writing software is to reduce it to a set of pieces that plug together, I'd agree. But it's an arbitrary metric and not an absolute measure of quality, not by a long shot.
Note that I don't say "composable", because composability is something that needs to be designed in, and it isn't usually clear how to do it best until the third or so time around - the rule of threes.
Furthermore, I don't say reusable, because reusability is something that also needs to be designed for. In particular, reusability in such a way that software can evolve over time without breaking clients (reusers) of the abstraction demands a tight and constrained contract that is broadened carefully, while testable components demand broad and flexible contracts, otherwise not everything would be available to be tested.
Every part that testing has forced you to break out to be individually addressable is a part that you cannot remove in a refactoring that significantly changes the way a library solves its problem.
A library that has been broken into parts that are neither composable nor reusable is simply over-complex. Almost every extant Java library is like this!
Of course, if you just write end software in small teams, none of this is relevant to you. But it is crucial in library design, especially when client code is outside of your organization.
When it works well, having two brains working together brings the benefit of different perspective and experience. It gives the opportunity to riff off each other's ideas. You spot issues with design and implementation earlier because having to communicate your ideas means working harder on them before you try to turn them into code.
I find it fun to work with someone else who is smart and engaged. It's magic when you become warmed up enough that things really start to flow. I've actually managed to get into flow before while working with someone else.
That said, it's pretty difficult to get right. I found it quite hard to let someone else see my process. If both sides aren't engaged in problem solving it can be really boring. I've also found that it takes me a while to figure out how to work productively with new people. The dynamic between any particular pair of people is a bit different. I think you need to build trust with your pair.
I think this highlights a deficiency in testing tools. It's quite hard to, for example, change the system time when running a test which often means that you have to abstract out that part from the method you're testing and pass it through as a parameter.
You shouldn't need to do that (in python you don't!).
Also, there is a lot of APIs out there with very real world effects and integrations that it is pretty cumbersome to build mock ups of. Most API providers also just don't.
Mocking what would happen, say, when a twitter oauth token expires, isn't as easy as it should be.
On the plus side, UI testing tools seem a lot better nowadays.
But yeah, there's a serious dearth in good testing tools and bad language design (cough Java) that ends up making code unreadable.
Passing the time into your function isn't necessarily a thankless chore, however. It's actually quite similar to strengthening your induction hypothesis when doing a proof. Now your function doesn't just claim to work correctly for one time (the implicit clock time) but for all times. This stronger claim (if true) makes it easier to reason about the code that relies on the function, including not only the testing code but also the rest of your application code.
To elaborate, I've tried pair programming myself and it was completely inefficient when we tried it. I'm not going to dismiss it entirely though, perhaps we approached it the wrong way. Personally I just need a bit of space before I can start focusing in-depth about certain problems.
This is also why I like to be well-prepared before attending team design decisions, because coming up with good ideas "right there and then" is difficult for me.
I pair on all production code at work with only two other guys. I've worked with them for the last year. Together, any combination of the three of us is easily twice as effective as the fastest in our team. Something about the rhythm of the session, alternating roles, support when tackling boring parts, and the camaraderie frees us up to just get stuff done.
But, we work in a very complex domain that, a year in and many seminars by product later, we only barely are starting to grasp, with a large difficult to grasp system, sometimes solving problems just outside our comfort zone. It's not just CRUD and forms. So, maybe pairing is the four wheel drive of the programming world: uses more gas on the highway, but depending on your terrain, it might be the most fuel efficient way to get across a mountain.
Or: I've tried Vim and my writing/editing speed halved. Or: I tried APL but it took me half a day to write one line of code. Or: I tried playing guitar and it sounded horrible.
I get the feeling that maybe the outcome would be different if you'd try doing it for a while longer. No guarantees, though.
I think the trick is to use it when developers feel necessary to stay productive. No point having someone slogging away at something they find difficult and frustrating if a second pair of eyes and maybe some more specific knowledge of the area can help.
What bothered me about how I saw pairing used was that people seemed to make blanket assumptions about it's benefits. Many times I saw people pairing up on trivially easy tasks. Seemed to me that pairing was a lot like everything else, it can be done well or poorly.
Pairing should be a naturally occurring process IMO; ie I don't know how to best accomplish a task or 'story', so I ask a team member w/expertise or experience to help point me in the right direction. If I need help beyond that, it becomes a pairing/knowledge-transfer exercise. I came to refer to it as "informal pairing". In general I tended to gravitate toward pairing on the exceptionally difficult tasks or ones that would have far reaching design implications.
I never bought into TDD, although I do write unit tests for most of the code where it makes sense.
No one has been able to show me an usable way to do TDD when coding native UIs, mobile OS, embedded systems or when using third party libraries not built with testing in mind.
Plus TDD makes very hard to properly design algorithms and data structures, that should beforehand be done at the whiteboard.
I agree. However, I do not consider this a strike against testing; I consider it a strike against native UIs, mobile OSs, embedded systems, and third party libraries that don't support testing. You may not do TDD (I generally don't), you may not strive for 100% coverage, but testing is a fundamental aspect of serious software engineering, and anything that actively fights your attempts to test it is a big strike against that tech. I only use the ones that fight you that hard because there's unfortunately no competition, but it's still a disgrace. In 2013, testing ought to be a fundamental first-class concern of any new UI library, yet here we are.
http://pragprog.com/book/jgade/test-driven-development-for-e...
I'm somehow surprised that this even works well with pairing of programmer and hardware engineer, but that requires management that believes in their engineer's skills (rapid iteration and hardware tends to get very expensive very fast) and programmers that have meaningful insight into hardware.
On the other hand projects I work on are probably not very representative of anything as most of them are weird :)
- Knowledge transfer. Learn new and better ways of working. IDE usage, short cuts, etc.
- On a complex piece of software is better to have two sets of eyes checking everything.
TDD. I enjoy doing it, I am not strict on doing test first, most of the time I don't. I usually shoot for 70% coverage. Indeed, the tests take a significant part of the effort, often they are brittle and you need to refactor them, but I really think they improve your overall design, your confidence on the robustness of the system.I've seen more people extoll (and consultants sell) automated testing than folks actually use it, let alone TDD. As an idea, I get it, but the implementation still seems mixed.
And I'm in the programming industry because I enjoy programming, so would want to work in an environment which I enjoy - and in today's market, i have the luxury of picking my employer.
I'm sure not everyone feels the same way, but I suspect I'm not alone in that opinion.
Is the result better than with careful code reviews [of the critical pieces] (both after writing, but also short checks during development over a code listing and coffee)?
Intuitively, everything with coffee involved ought to be better! :-)
That being said, I do miss solo work, because I could get into the zone, and even if I was going down the wrong path, it was ME doing it, master of my own domain. It feels like I'm alone on my boat sailing into uncharted waters, an adventure.
At its best, pairing feels like being part of a tactical response team, at its worst: like there is a machine that turns my brain cycles into money, and they let me keep some of the money at the end.
Edit: Rereading both comments, apparently pairing makes me wax with metaphors, like a... nah
http://www.craiglarman.com/wiki/downloads/misc/history-of-it...
And before that for other activities: "Plans are nothing; planning is everything." - Dwight D. Eisenhower
"A complex system that works is invariably found to have evolved from a simple system that worked."
He's one of the good guys, that's for sure - but every time I read his blog I keep waiting for that something new and it never comes.
Many companies are still run waterfall like, or without any kind of process.
When we bring in agile methodologies into the project, it starts slowly, but eventually everything is in place and everyone is doing it in an agile way (XP, SCRUM, whatever).
When the first project escalation arrives, or the deadlines are not possible to be achieved, the developers start slowly going back to the original way of working.
In the end you get mini-waterfall projects with a sprint duration, but the management puts agile on the project bullet points.
For most enterprises automated build, test, deploy (ie CI/CD) is the one missing tool and one absolutely necessary tool to capture and keep benefits of agile - it's capital.
And also for most enterprises you could lose 1/3 of the IT staff without noticing.
From June 2011, this article covers four key success factors for the next 10 years of agile. Great thinking on software development.
There are definitely tasks in which I would avoid pairing - specifically those that are either very ill-defined (like a spike or bug hunt) or too easy (write some data transformations). However, the tasks that should result in a clean, well tested API with edge cases taken care of, tend to be higher quality while pairing in my experience.