While I was refactoring my app’s state recently, I noticed that there are virtually no dev logs about The Archive and how I manage state and events in this app. It’s my largest app so far, still the amount of reflection and behind-the-scenes info is pretty sparse. This post outlines my recent changes to the app’s state. It’s a retelling of the past 5 weeks or so.
Shopify published amazing/epic stats for their Ruby on Rails “app”:
1:1.3 code–test ratio
2300 model classes
most classes <10 methods
most methods way <10 lines
Makes you wonder:
can you live with more types?
can you shrink down existing types?
Swift changed the game a bit. Now it’s very convenient to create a new type. You don’t need to do the .h/.m file dance anymore but can start a new type declaration wherever you are. (Except when you’re inside a generic type.) That makes it more likely we devs stop to shy away from smaller types.
But it’s still a job for our community’s culture to unlearn coding huge classes (massive view controller syndrome, anyone?) and splitting stuff.
Ruby has a long tradition of super-focused methods and very small classes. Swift is just as terse and powerful in that regard. Now it’s your turn to experiment with writing types for very simple tasks. Like the Extract Parameter Object refactoring, where you lift things that go together into a new type.
Can be as easy as writing:
Et voilà, there you have a new explicit concept in your code base.
Let’s say you have a Presenter that creates a ViewModel for its View from incoming data and then presents it in said view. In this case, the Presenter also is a ReSwift.StoreSubscriber for good measure, but it could receive the data by any means, really.
When facing a legacy code base, changing the mess to an orderly architecture can cause confusion: where to start? What to do? An exemplary question: How do I refactor a Big Ball of Mud into layered architecture? Refactoring the existing code base seems like a logical step; after all, refactorings are designed to improve existing code, and improvement is what you’re up to.
Literals represent something. Magic numbers are a form of literal. This goes back at least as far as the book Edward Yourdon and Larry L. Constantine (1979), Structured design (affiliate link), on page 94. The number 79 can truly represent the result of 80-1.
I executed my plan from earlier this week to split TableFlip’s monolithic view model into sub-view models. The process wasn’t too complicated. The highlights: The current hierarchy of controllers in the view layer is as follows: The window doesn’t look too complicated and there truly are not that many view components involved, but still it’s quite some work to keep things well coordinated. The TableViewController file clocks in at about 400 lines of code although it mostly delegates stuff to other objects. There’s still room for improvement, but I have to see patterns, first. I even extracted NSTableViewaDataSource and NSTableViewDelegate into different objects months ago. Today I doubt this was a good idea in the first place. We’ll see.
The upcoming changes in TableFlip’s user experience made me ponder how I structured the app’s modules. The bottleneck I created in my app is simple: there’s one presenter which transforms the model into a view model; the presenter defines a protocol for the view it expects and passes the view model to the object that satisfies this contract.
The location of a piece of code always matters. I was fiddling with the Charts library today and found out that if you set a chart’s data to nil, it renders some informative text by default. My data source doesn’t pass nil but an empty array around, though, so I had to convert empty arrays to nil to make use of this feature.
When parameter lists grow or two kinds of parameters seem to go together a lot, it’s time use the extract parameter object refactoring for greater good – then you can even specify sensible defaults.
Now Soroush wrote about a way that uses the Then microframework as a replacement for configuaration dictionaries. This way you don’t have to promote every property to the initializer’s list of parameters. Here’s a before and after, where you can see that without then you have to write a lot of repeating boilerplate:
Daniel Steinberg’s presentation “Ready for the Future: Writing Better Swift” teaches us a lot about readable code. He refactors a calculation into many functions with very specific responsibilities. The resulting functions are super slim.
Apart from the » operator which merely changes foo.map(bar) to foo » bar, the functions are very small, easy to read. This reminds me of the result of 3/4 of Sandi Metz’s arbitrary rules for writing Ruby code:
Classes must be shorter than 100 lines
Methods must be shorter than 5 lines
Always pass less than 4 parameters into a method
Of course the result is a lot of functions for a rather simple algorithm. But it works well because it is easy to read in the long term. Even newcomers can grasp what’s going on without knowing much about the language or typical Cocoa-programmer conventions.
Bonus: you can unit test each function to check if the parts of the overall algorithm works as expected.
A refactoring in a side project requires finding places where a property we want to get rid of is currently used. Finding the property name in the project will not work very well because other types use a similar named property. We only want to remove Banana.size, but not Jeans.size.
I watched an AltConf about API design the other day. I cannot seem to find which talk it was, though. Anyware, the presenter talked about using parameter objects when the parameter list grows too long or is open for change in future versions. Parameter objects can change internally and evolve with the API. You can add or remove attributes, for example, while the API calls of old client code don’t have to change: they still pass the same type in. That makes framework updates a bit less painful because method signatures stay the same.
View Controllers should only be responsible for view lifecycle events, Marcus Zarra reminds us. That means they should populate views with data, and show and hide them – stuff like that. View controllers should not do the work of views.
While I like his overall advice, it contains a few problematic points:
Put Core Data out of the view controller. His solution to provide a single data source tied to a NSManagedObjectContext will not scale well as I’ve experienced. – Still, don’t tie it to the view controllers. That makes matters worse.
His definition of “business logic” is odd. Business logic is not reducible to I/O code. It’s not just about fetching data from a device and doing network requests – except when your app is a very, very simplistic display for data with CRUD operations. Business logic can exceed data-centric rules and entail a real domain with complex behavior without much imagination.
Views should be responsible for displaying data, agreed. Since validation problems will be presented to the user, validation error messages will be part of that data, too. But views should not validate. Validation rule objects (WWDC’14 talk) should be the strategies that do validation.
If we create an object to sit between the view and the view controller then we are creating unnecessary additional objects.
Where do presenters sit? Between view and and view controller – but they make it easier to read and extend the code. They’re hardly unnecessary.
I strongly believe that it’s impossible to give advice about how to code and architect something if the problem domain isn’t part of the example. Because everything depends on the problem space in the end.
Remember to take every advice with a grain of salt. Collect things like Marcus’ tips to obtain new tools, but don’t take the tool for the solution of modelling a program to solve real problems.
Integrating new functionality is fun. But revisiting 18-months old code isn’t. Back then I created a protocol InvokesWindows to define methods like -showPreferencesWindow which I imported in the menu bar controller to show the preferences when the user selects a pop-up menu item. But I didn’t actually delegate to any instance of InvokesWindows. I used NSApp. (Insert facepalm here.)
Refactoring Legacy Code is hard. There are a few safe refactorings you can do with caution. But most chirurgical cuts require you to put the code in a test harness first to guard against regression. With C and Swift, you can create free functions as part of your app. To verify your objects use that function, you need to find a way to insert a test double.
There’s a WWDC 2014 talk called “Advanced iOS Application Architecture and Patterns”. In the first 30 minutes, you can learn a lot about designing information flow in your app. Sticking to Andy Matuschak’s example, a view controller is usually the place to put all behavior. (Hint: this is a bad idea.)
I’m refactoring code of Calendar Paste 2 some more. Since I like what I learned using Swift so much, one of today’s changes was about making a view controller method do less by factoring object creation and error handling into its collaborators. The resulting code handles far less of the action involved. Instead, it delegates the action to a new command-like object. And instead of querying an event template for data and assembling an actual event with start and end dates, it delegates event creation to the template itself. Tell, Don’t Ask saved the day.
When we create applications and the business logic becomes more complex, it might be a good idea to focus on moving business logic into the Domain Model intentionally. If you don’t do this, business logic will likely bleed into view controllers. Good luck finding the scattered remains when you need to perform changes!
Like I promised last weekend, I am going to write about the process of cleaning up the already rotten source code of Calendar Paste. In order to break massive view controllers into manageable pieces and un-tangle everything, I have to make sure that I don’t break the current implementation. Calendar Paste didn’t have any automated tests in place. To change this fact is my first priority.