I'll start by saying the book Refactoring Typescript (paperback, ebook) by James Hickey was named incorrectly. Having read it, I would have called the book something like the Refactoring Toolset or Refactoring Pearls. The book is an honest refresher on ways code becomes messy and offers tools to clean this up. It uses the pleasant to read language Typescript to showcase techniques, and the author also occasionally mentions C# and Java as examples.
I enjoyed reading the book and took some learnings from it. This is despite not having used Typescript and not planning to do so in the future. Most of the capabilities the book mentions exist in modern OO languages like C#, Java, or Swift - languages that offer more advanced features beyond what the book dived in.
The structure of the book is walking through problem areas, one by one. It starts with messy code that you might see in the real world, takes a hard look at it, refactors it, and reflects on the results. This simplicity, the focus on the code, and on how the code is refactored is refreshing. I often found the code is more interesting and engaging than the surrounding content. I found myself debating several times “hmm, how would I refactor this, and why?”, before reading on.
So what is refactoring? James kicks off the book with a straight-to-the-point definition:
Refactoring is just a fancy term that means improving your code without changing how it behaves.
I liked this start. I was also okay with the next part, where it mentions
It’s important to have your code under test (unit, integration, etc.), which will give you a safety net to ensure any code changes won’t also change the behavior and cause bugs.
However, I was somewhat disappointed that this was the first and last mention of testing related to refactoring. The two go hand-in-hand to the point that good testing might make some refactoring practices less relevant.
Situations, where refactoring could be helpful, is covered pretty well in the book. Almost all examples are looking at a class, or a part of a class. It takes problem areas from null checks through issues with conditionals, verbose methods, and other, common problem areas. The book doesn’t try to go beyond the function- or class-level problem space, into some of the more hairy problems of complex systems, and it keeps for an easy read because of this. Refactoring and untangling large systems and services is its own, much more messy topic, where architecture and the people who wrote the code are both hard to ignore.
Parts I particularly enjoyed about the book was mainly, the simplicity of the structure, and the plenty of “messy” code samples, that got cleaner, step-by-step. While the book is fine to read on a Kindle, I quickly switched to a laptop and tablet to scan the code quicker. I liked how all problems and refactor approaches were applicable to all strongly typed OO languages I know and use, like - Java, Swift, Go, or C#.
Where I would have enjoyed seeing more content and depth are:
- Testing. Testing and refactoring go hand-in-hand, and I would have enjoyed reading some firsthand examples of why this approach is key. Engineers who refactor without testing are playing some level of Russian roulette.
- Semantic meaningfulness and correctness. The book touches on clarity and semantic meaning a few times. Expanding this topic, with examples, could have been an interesting one.
- Naming and naming conventions. Much of the book is about slicing and dicing code, introducing semantically meaningful variables, functions and classes. However, there was no discussion about what good naming can be and how different teams and projects might agree on different naming standards. And it would have been nice for the book to explain the naming standards it follows. For one, I felt a little jerk every time I saw a boolean variable that did not have a “hasXX”, “isXX”, “canXX” semantic prefix in front of it, as this is the naming style I usually follow.
- Language features that make refactoring easier. The book goes into detail on guard statements and classes. Swift 2.0 introduced the guard statement to make this setup easier.
New things I learned, after reading the book:
- The special case pattern: the approach of recognizing special cases, and considering making this logic the responsibility of a class, instead.
- Piping classes for conditional checking as a means to refactor too many “if” conditions. The whole piping approach was interesting, but it goes too far for me to use. I see it as one of those “too clever to be useful” cases.
- CQRS and splitting classes based on “read” or “write” functionality. CQRS stands for Command Query Responsibility Segregation. James suggests creating two different presenter/model classes, based on if they are only displaying data (e.g. displaying payment information for a user) or also used to write/persist data. In this example, he'd suggest having a
PaymentUserForWrite. I still have to decide if this is something I'd definitely want to use, but it's something interesting to chew on.
Overall, I can see the book being a nice and easy read for both beginners and experts, with takeaways for both groups. Beginners in coding, or in Typescript will find practical advice on refactoring code, and an overview of a few Typescript features like nullable types and optional parameters. Unfortunately, beginners will not have experienced much of the pain points of legacy code - but when they do, this book can be a good start on ways to deal with them.
Experienced engineers will find this book both a little refresher and will likely find one or two refactor approaches they didn’t know about. In my case, the CQRS approach was new. Also, for experienced engineers, the book is a light read, but also a nice brain teaser. As you’re reading the book, ask, “How can I summarise what is wrong with this code?” and “What tools could I use to make it better?”. I consider myself in the experienced engineer group and enjoyed going through the code samples, one after the other.
Featured Pragmatic Engineer Jobs
- Senior Backend Engineer - C#/.NET at Straddle. £90-125K + founding team equity. Remote (UK).
- Senior Solutions Engineer at Tint. $130-195K. Remote (US).
- Product Engineer at Causal. Remote (US, UK). The team tackles interesting challenges like simplifying React state management.
- Backend Engineer - Data at Causal. Remote (US, UK).
- Senior Backend Engineer at Polarsteps. Amsterdam (Netherlands).
- Senior Data Engineer at GetHarley. £70-100K. Remote (UK) or Hybrid.
- Senior Frontend Engineer at GetHarley. £70-100K. Remote (UK) or Hybrid.
- Senior Software Engineer at Tint. $140-195K. Remote (US).
- Senior Product Engineer, Frontend at Attio. £90-125K + equity. Remote (Europe).
- Senior Data Engineer (RoR) at Terminal49. $140-200K. Berkeley, California.
- Engineering Manager - Security Product team at CAST AI. Remote (Lithuania).
- Software Engineer at Freshpaint. $130-210K + equity. Remote (US).
The above jobs score at least 10/12 on The Pragmatic Engineer Test. Browse more senior engineer and engineering leadership roles with great engineering cultures, or add your own on The Pragmatic Engineer Job board and apply to join The Pragmatic Engineer Talent Collective.
Want to get interesting opportunities from vetted tech companies? Sign up to The Pragmatic Engineer Talent Collective and get sent great opportunities - similar to the ones below without any obligation. You can be public or anonymous, and I’ll be curating the list of companies and people.
Are you hiring senior+ engineers or engineering managers? Apply to join The Pragmatic Engineer Talent Collective to contact world-class senior and above engineers and engineering managers/directors. Get vetted drops twice a month, from software engineers - full-stack, backend, mobile, frontend, data, ML - and managers currently working at Big Tech, high-growth startups, and places with strong engineering cultures. Apply here.