10 Ways To Improve Your Code
Sr. Software Architect / Meme Wrangler
Where did this topic come from?
Book: “The Productive Programmer”, Neal Ford
Exists in two parts, “mechanics” and “practices”.
Mechanics: how to speed you up, learning keyboard shortcuts.
Today is more Practical (and philosophical).
“The unexamined life is not worth living.” — Socrates
“Unexamined code isn’t worth executing.” — Neal, stealing from Socrates
1. Test driven design.
When you’re really rigorous at test-driven development, it has effects on your design. You’re creating the first consumer of your code.
- Consumption awareness. You’re aware of how the rest of the world will use the code, before they use it.
- Mocking of dependent objects. If you’re really unit testing, you’re mocking out the dependencies, which forces you to think about the collaboration between these objects.
- Introverted vs. extroverted objects:
- If you do TDD, you’ll learn to avoid “extroverted objects” that reach out to create other objects, fire constructors, allocate resources, etc.
- Instead, you’ll use “introverted objects” where all dependencies are injected via parameters, properties, etc. You tend to move object creation to a few simple places, and can use things like real dependency injection. Also called “shy objects”.
2. Metrics and static analysis.
Compilation is a really, really weak form of unit testing. You can compile any sort of trash. “It compiles” doesn’t tell you much.
Source Monitor (can do many languages including C# and Delphi). Lots of metrics, including cyclomatic complexity. Can set thresholds and have it show the outliers. Has both GUI and command line. Costs you virtually nothing, and will help you find problem spots.
Cyclomatic complexity (1978): measure the complexity of your code. Has to do with the number of paths through the code, though it’s not quite that simple; it’s based on number of branches and decisions. But higher cyclomatic complexity tends to correlate to more bugs.
3. Good Citizenship.
Cooperation with the rest of the world.
Properties != Encapsulation.
- See too many devs who use a tool to spit out public read/write properties for every one of their fields, and then engage their brain.
- “Knee-jerk propertiation.”
- Much better off passing values to a setter method like SetAddress, which means you can’t be a bad citizen by e.g. setting City without setting State — your object should go atomically from one good state to another good state.
- Objects are the keepers of state, and they should guard that jealously.
- Only create properties when you need to call them from real code.
- Specific contract for how to create valid objects.
- How often is a valid object blank? (from a business standpoint) … Never!
- Don’t provide default constructors for domain objects.
- Push back on frameworks that try to require this
- If you need a null object (and have a language that requires you to have a stateful object to say you have no state)…
- Should be a black box
- You never worry, when you call the
sqrtfunction, that the next time you call it it’s going to give you the cube root.
- Statics should be non-stateful.
- Where you get problems is when you mix statics with state.
Mixing static + state
- The Evil Singleton Pattern
- Bad because
- Mixes responsibilities (doing stuff and maintaining its instance)
- Object version of global variables
- Better: object and factory, where the factory handles the instantiation policing
- Factory can even call object’s private constructor through reflection. Then you’re enforcing that nobody can instantiate the singleton accidentally.
Picture of gold-plated toilet
- Build simplest thing we need right now!
- No speculative development. Speculative development:
- Increases software entropy. The more code, the more complexity, even if you aren’t using it.
- Only saves time if you can guarantee you won’t have to change it later
- Leads to frameworks.
- Frameworks are not inherently bad, but we have framework-itis.
- Let’s face it, building a framework is cooler than what you should be doing right now.
- Frameworks are written by people engaged in ivory-tower building.
- The best frameworks are not created by somebody who wants to build a framework. The best are extracted from working code.
Changeability. Code with anticipatory design is larger and harder to refactor and maintain.
5. Shaving with Occam
Sir William of Occam: “Given multiple explanations, the simplest is best.”
Simplest is really hard to come by.
Dave Thomas was brought in to solve problem with volume of internal mail. Inter-office memos were constantly getting misrouted. So they brought in an OCR machine, and brought Dave in to write software for this thing. At one point, Dave said, “Couldn’t you solve this problem with colored envelopes?”
It’s really hard to understand the essence of the problem and address that.
We are drowning in complexity.
- Essential complexity: We have a hard problem that we’re trying to solve.
- Accidental complexity: We’ve made the problem hard.
- Simplify essential complexity
- Kill accidental complexity
This also affects the kind of tools you use. E.g., “static” vs. “dynamic” languages. (I already noted this “ essence vs. ceremony“.) Something nasty and multithreaded would be better written in something like F#.
Interlude: the Top Ten Corporate Code Smells
- We invented our own web / persistence / messaging / caching / logging framework because none of the existing ones was good enough.
- We bought the entire tool suite (even though we only needed about 10% of it) because it was cheaper than buying the individual tools.
- We use BizTalk because… (I always stop listening at this point) Okay, it has some legitimate uses, but it’s been much abused.
- We can’t use any open source code because our lawyers say we can’t.
- We have an Architect (note the capital A) who reviews all code pre-checkin and decides whether or not to allow it into version control. (New feature in VSTS.)
- The only XmlDoc is the default message explaining how to change your default XmlDoc template.
- We keep all of our business logic in stored procedures… for performance reasons. There is some tortured logic at work here, but that ignores that dealing with stored procs will slow down your development. Let’s have a talk about premature optimization.
- We don’t have time to write unit tests — we’re spending too much time debugging.
- The initial estimate must be within 15% of the final cost, the post-analysis estimate must be within 10%, and the post-design estimate must be within 5%. (Punished for coming in too low as well as for coming in too high. The team will overestimate on purpose, get done early, not tell you about it, twiddle their thumbs until they hit the golden window, and then say, “Okay, all done!”)
- Blue Screen of Death.
Side note: Unless you work for NASA, your problem isn’t harder than everyone else’s. You’re just doing it wrong.
6. Question authority.
Dave Thomas’ story of angry monkeys. (The story of the stepladder, the bananas, and the ice water. “That’s the way we’ve always done it.”)
[Test] public void UpdateCacheAndVerifyThatItemExists()
Long camel-cased names are hard to read, and test names tend to be long. Neal’s suggestion: use underscores just for test names.
[Test] public void Update_cache_and_verify_that_item_exists()
ICar car = Car.describedAs() .Box .Insulated .Includes(Equipment.Ladder) .Has(Lining.Cork);
instead of setting lots of properties, to make it more readable for non-programmers. Thing is, it violates the rules of properties, by making a property getter (Box) that mutates the object. But in the context, it makes for much clearer code.
- Interfaces define semantic intent
- No implementation details…
- …except in the name!
- Don’t name interfaces with a leading “I”
- Name concrete classes with a naming pattern
- (I’m not sure that I would agree with Neal on this, but it’s worth thinking about)
What’s Bad About Standards?
- Forces you to create default constructors
- Property setters return void
- Can’t use PONOs for fluent interfaces
- Pair programming (seems like it would be 50% slower, but in reality, once you settle into it, you develop 15% slower, but have 50% fewer defects)
- GiveMeEstimatesNow. Presented with a problem, asked for an estimate out of the blue; you don’t know anything about it, but the boss asks for a guess. That becomes an ironclad contract.
- StandingOnTheShouldersOfMidgets. Have a disastrous tool or framework that you’re required to use in every application, because it’s a corporate standard.
Anti-patterns are the lore of software. Take advantage of this prior art. Don’t think that the problems at your job are uniquely yours.
7. Composed Method.
“Smalltalk Best Practice Patterns” by Kent Beck. Smalltalk people were some of the first OO programmers, and came up with elegant solutions. (They’ve got a head start on the rest of us.)
- Every public method consists of steps implemented as private methods.
- You can tell something’s wrong with the existing code if there have to be comments.
- Extract methods.
- Find opportunities for code reuse. Some of those methods you extract might start to look like Template Methods, which opens up more opportunities.
8. Polyglot Programming
Leveraging existing platforms with languages targeted at specific problems and applications
We have lots of .NET languages. Why not take advantage of them, rather than thinking there’s a framework for everything that you can use from the One True Language?
Looming problems / opportunities
- Massively parallel processing (multithreading)
- Use a functional language: F#, Haskell.net
- Schedule pressure
- Use a dynamic language: IronPython, IronRuby
- Use an alternate Web framework: Django, Ruby on Rails
- New approaches
- Domain-specific languages (DSLs)
- Fluent Interfaces
Write your multithreaded logic in F#, your GUI in C# or VB.NET, your unit tests in IronPython or IronRuby, and use DSLs
- Wouldn’t that add complexity?
- In the past, language == platform
- Now, language != platform
- Stable language at the bottom, e.g. C#. Static-typed, statically-verified. Maybe even stricter than C#, like a mathematical language.
- Above that, a dynamic (non-ceremonial, e.g. F#) language that lets you write code quickly.
- Above that, a DSL that gets you closer to the problem domain.
9. Learn Every Nuance
- Dependency injection
- Lambda expressions
- Extension classes
- And once you’ve learned it, teach it to your co-workers. E.g., regular expressions.
- Can save you orders of magnitude of work.
Comes from OOPSLA paper called “Collaborative Diffusion”.
“The metaphor of objects can go too far by making us try to create objects that are too much inspired by the real world.”
Figure vs. ground. Sometimes you need to see the problem a different way.
Pac-Man console had less memory than a 10-year-old cell phone. Problem to solve for the ghosts: What is the shortest distance between two moving objects in a maze? Solution: don’t model the ghost; model the maze. Intelligence was built into the maze itself. Each cell had state. Invented “Pac-Man smell”. A cell he just moved off of had “maximum smell – 1”, and it decayed quickly. Ghosts just had to smell Pac-Man. The ghosts wander randomly until they pick up the scent, and then they move into the cell with higher smell. So they will never cut him off at the pass (on purpose)… but it was a very simple and memory-efficient solution.