2013-06-11

Teaching a Developer to Fish

I write a lot about development philosophy here, and very little about technique. There are reasons for this, and I'd like to explain.

In my experience, often what separates an easy problem from an intractable one is method and mindset. How you approach a problem tends to be more important than the implementation you end up devising to solve it.

Let's say you're given the task of designing a recommendation engine - people like you were interested in X, Y, and Z. Clearly this is an algorithmic problem, and a relatively difficult one at that. How do you solve it? 

The algorithm itself isn't significant; as a developer, the algorithm is your output. The process you use to achieve the desired output is what determines how successful you'll be. I could talk about an algorithm I wrote, but that's giving a man a fish. I'd much rather teach a man to fish.

So how do you fish, as it were, for the perfect algorithm? You follow solid practices, you iterate, and you measure. That means you start with a couple of prototypes, you measure the results, you whittle down the candidate solutions until you have a best candidate, and then you refine it until it's as good as it can get. Then you deploy it to production, you continue to measure, and you continue to refine it. If you code well, you can A/B test multiple potential algorithms, in production, and compare the results.

How do you fish for a fix to a defect? You follow solid practices, you iterate, and you measure. You start by visual inspection, checking for code quality, and doing light refactoring to try to simplify the code and eliminate points of failure, to narrow down the possibilities. Often this alone will bring the root cause of the defect to the surface quickly, or even solve it outright. If it doesn't, you add logging, and you watch the results as you recreate the error, trying to recreate it in different ways, to assess the boundaries of the defect; if this is for an edge case, what exactly defines the "edge" that's affected? What happens during each step of execution when it happens? Which code is executing and which code isn't? What parameters are being passed around?

In my experience, logging tends to be a far more effective debugging tool than a step-wise debugger in most cases, and with a strong logging framework, you can leave your logging statements in place with negligible performance impact in production (with debug logging disabled), and with fine-grained controls to allow you to turn up verbosity for the code you're inspecting without turning all logging on and destroying the signal-to-noise ratio of your logging output.

You follow solid practices, you iterate, and you measure. If you use right process, with the right mindset, you'll wind up at the right solution.

That's why I tend to wax philosophical instead of writing about concrete solutions I've implemented. Chances are I wrote the solution to my problem, not your problem; and besides, I'd much rather teach a man to fish than give a man a fish.

2013-06-08

My Present Setup

I thought I'd take a quick moment to lay out my current setup. It's not perfect, it's not top-of-the-line (nor was it when any of the parts were purchased), it's not extravagant, but I find it extremely effective for the way I work.

The Machine (DIY Chronos Mark IV):

  • Intel Core i5 750 LGA1156, overclocked from 2.6GHz to 3.2GHz
  • ASRock P55 Extreme
  • 8GB DDR3 from GSkill
  • ATi Radio HD 5870
  • 256GB Crucial m4 SSD (SATA3) - OS, applications, caches & pagefile
  • 2 x 1TB Seagate HDD - one data drive, one backup drive
  • Plextor DVD-RW with LiteScribe
I find this configuration to be plenty performant enough for most of my needs. The only thing that would prompt an upgrade at this point would be if I started needing to run multiple VM's simultaneously on a regular basis. The GPU is enough to play my games of choice (League of Legends, StarCraft 2, Total War) full-screen, high-quality, with no lag. The SSD keeps everything feeling snappy, and the data drive has plenty of space for projects, documents, and media. The second drive I have set up in Windows Backup to take nightly backups of both the primary and data drives.

My interface to it:
  • Logitech G9x mouse (wired)
  • Microsoft Natural Elite 4000 keyboard (wired)
  • 2 x Dell U2412M 24" IPS LCD @ 1920x1200
  • Behringer MS16 monitor speakers
If you couldn't tell, I have a strong preference for wired peripherals. This is a desktop machine; it doesn't go anywhere. Wireless keyboards I find particularly baffling for anything other than an HTPC setup; the keyboard doesn't move, why would I keep feeding it batteries for no benefit? The mouse is an excellent performer, and I love the switchable click/free scroll wheel (though I wish the button weren't on the bottom).

The displays are brilliant and beautiful, they're low-power, I definitely appreciate the extra few rows from 1920x1200 over standard 1080p, and having two of them suits my workflow extremely well; I tend to have one screen with what I'm actively working on, and the other screen is some combination of reference materials, research, communications (chat, etc.), and testing whatever I'm actively working on. Particularly when working with web applications, it's extremely helpful to be able to have code on one screen and the browser on the other, so you can make a change and refresh the page to view it without having to swap around. These are mounted on an articulated dual-arm mount to keep them up high (I'm 6'6", making ergonomics a significant challenge) and free up a tremendous amount of desk space - more than you'd think until you do it.

The Behringers are absolutely fantastic speakers, I love them, to death, and I think I need to replace them. I recently rearranged my desk, and since hooking everything back up, the speakers have a constant drone as long as they're turned on, even with the volume all the way down. I've swapped cables and fiddled with knobs and I'm not sure the cause.

The network:
  • ASUS RT-N66U "Dark Night" router
  • Brother MFC-9320CW color laster printer/scanner/copier/fax (on LAN via Ethernet)
  • Seagate 2TB USB HDD (on LAN via USB)
The RT-N66U or "Dark Night" as it's often called is an absolutely fantastic router. It has excellent wireless signal, it's extremely stable, it's got two USB ports for printer sharing, 3G/4G dongle, or NAS using a flash drive or HDD (which can be shared using FTP, Samba, and ASUS' aiDisk and aiCloud services). The firmware source is published regularly by ASUS, it's Linux-based, and it includes a complete OpenVPN server. It offers a separate guest wireless network with its own password, which you can throttle separately and you can limit its access to the internal network. It has enough features to fill an entire post on its own.

Mobility:
  • Samsung Galaxy S4 (Verizon)
  • ASUS Transformer Prime (WiFi only)
The SGS4 is an excellent phone, with a few quirks due to Samsung's modifications of the base Android OS. The display is outstanding, the camera is great, the phone is snappy and stable, and it has an SD card slot. That's about all I could ask for. The tablet I bought because I thought it would make an excellent mobile client for my VPN+VNC setup; unfortunately, I've had some issues getting VNC to work, and now that I'm on a 3840x1200 resolution, VNC @ 1080p has become less practical. However, it still serves as a decent mobile workstation using Evernote, Dropbox, and DroidEdit.

All in all, this setup allows me to be very productive at home, while providing remote access to files and machines, and shared access to the printer and network drive for everyone in the house. The router's NAS even supports streaming media to iTunes and XBox, which is a plus; between that, Hulu, and Netflix, I haven't watched cable TV in months.

2013-06-07

Code Patterns as Microevolution

Code patterns abide by survival of the fittest, within a gene pool of the code base. Patterns reproduce through repetition, sometimes with small mutations along the way. Patterns can even mate, after a fashion, by combining them, taking elements of each to form a new whole. This is the natural evolution of source code.

The first step to taming a code base is to realize the importance of assessing fitness and taking control over what patterns are permitted or encouraged to continue to reproduce. Code reviews are your opportunity to thin the herd, to cull the weak, and allow the strong to flourish.

Team meetings, internal discussions, training sessions, and learning investments are then your opportunity to improve both the quality of new patterns and mutations that emerge, as well as the group's ability to effectively manage the evolution of your source, to correctly identify the weak and the strong, and to have a lasting impact on the overall quality of the product.

If you think about it, the "broken windows" problem could also be viewed as bad genes being allowed to perpetuate. As the bad patterns continue to reproduce, their number grows, and so does their impact on the overall gene pool of your code. Given the opportunity, you want to do everything you can to make sure that it's the good code that's continuing to live on, not the bad.

Consider a new developer joining your project. A new developer will look to existing code as an example to learn from, and as a template for their own work on the project, perpetuating the "genes" already established. That being the case, it seems imperative that you make sure those genes are good ones.

They will also bring their own ideas and perspectives to the process, establishing new patterns and mutating existing ones, bringing new blood into the gene pool. This sort of cross-breeding is tremendously helpful to the overall health of the "code population" - but only if the new blood is healthy, which is why strong hiring practices are so critical.

2013-06-06

The New GMail for Android

The new GMail for Android UX sucks. I mean... it's really awful.

They've replaced the checkboxes next to each message (useful) with sender images (gimmick), or, if there is no sender message (i.e., everything that's not a G+ contact - so, every newsletter, receipt, order confirmation, etc. you'll ever get), a big colorful first initial (completely useless waste of space). This image then acts as if it were the checkbox that used to be there (confusing) for selecting messages. You can turn off the images, but you don't get the checkboxes back; you can only tap-hold to select multiple messages, though this isn't mentioned anywhere, you just have to guess.

They've gotten rid of the delete button (why?), and moved it to the menu.

If you have no messages selected, pressing the device's menu key gives you the menu. However, if you do have messages selected, the menu key does nothing, instead you must tap the menu button that appears at the top-right of the display. It's not there if you don't have messages selected.

Once you're viewing a message, there are two menus: one when you tap the menu button, with 90% of the options in it, and another at the top-right gives you just two options, forward and reply-all; this almost makes sense, except that it uses the same, standard "here's the menu" button that's used on (some) other screens as the *only* available menu.

In the message view they've also gotten rid of the delete button (to match the annoyance of the message list, I supposed).

There is also a new "label settings" screen that's fairly mysterious; I assume it applies to the current label, though this includes "Inbox", which - while I understand it's treated internally as a label - I think most users don't think of as being a label in the typical sense.

2013-06-02

Building a Foundation

It's been said that pharmaceutical companies produce drugs for pennies per pill - except the first pill, which costs millions. Things aren't so different in the land of software development: the first usage of some new functionality might take hours, building the foundation and related pieces. But it could be re-used a hundred times trivially, and usually expanded or modified with little effort as well (assuming it was well-written to start with).

This is precisely what you should be aiming for: take the time to build a foundation that will turn complex tasks into trivial ones as you progress. This is the main purpose behind design concepts like the single responsibility principle, the Hollywood principle, encapsulation, DRY, and so on.

This isn't to be confused with big upfront design; in face, it's especially important to keep these concepts in mind in an agile process, where you're building the architecture as you go. It can be tempting to just hack together what you need at the moment. That's exactly what you should be doing for a prototype, but not for real development. For lasting functionality, you should assemble a foundation to support the functionality you're adding now, and similar functionality in the future.

It can be difficult to balance this against YAGNI - you don't want to build what you don't need, but you want to build what you do need in such a way that it will be reusable. You want to save yourself time in the future, without wasting time now.

To achieve a perfect balance would require an extraordinary fortune teller, of course. Experience will help you get better at determining what foundation will be helpful, though. The more experience you have and the more projects you work on, the better sense you'll have of what can be done now to help out future you.