(Apologies in advance if this is more scattered than usual—trying to distract myself from the most painful parts of the first Clinton/Trump presidential debate by blogging.)
We had some fairly significant studying to do over the weekend, so I’m splitting it into its own section.
The SOLID Principles
We read about SOLID design principles, starting with “Design Principles and Design Patterns” by Robert C. Martin. Delving into these five principles helped scratch some of the itch I’ve been feeling since handing in my Tic-Tac-Toe project: sometimes jumping in and hacking bits off of a large project and tackling whatever I can tackle in order to break the problem down and get it done is exhilarating. Piecemeal != coherent, though, and it’s nice to know there are established guidelines for thinking about a more orderly approach.
The basic gist: bad design is caused by bad dependencies. It’s rigid (difficult to change), fragile (one change causes multiple breaks), immobile (code can’t be easily reused), and viscous (optimal changes are too hard and take too long; it’s easier and faster to circumvent the system). Good object-oriented design uses “dependency firewalls” to help avoid these problems.
- Single Responsibility Principle: Classes should only do one thing.
- Open Closed Principle: “A module should be open for extension but closed for modification.” Write modules that can be extended without having to modify them. Rely on abstraction: don’t hard code in a bunch of if/else or switch statements that lock down specific behaviors within a module.
- Liskov Substitution Principle: “Subclasses should be substitutable for their base classes.” Subclasses should honor the “contracts” of their base classes—they shouldn’t have stronger preconditions (i.e., they shouldn’t need additional things to be true before they’re called), and they shouldn’t have weaker postconditions (i.e., they shouldn’t guarantee that fewer things are true after they’re called).
- Interface Segregation Principle: Don’t overload a class with methods that all its clients don’t need. It’s better to have multiple small, specific client interfaces than a single large, general interface. Separate methods into different interfaces, and inherit only those that are necessary into a class.
- Dependency Inversion Principle: “Depend upon Abstractions. Do not depend upon concretions.” High-level things shouldn’t rely on mid-level things that rely on detailed things. Instead, everything should rely on abstract things, which are less likely to break or be changed.
Sandi Metz’s rules
Sandi Metz is a developer and author who specializes in object-oriented programming. Her four rules for developers are focused on keeping things clean and modular in order to help adhere to the SOLID principles. The rules are:
- Class shouldn’t be >100 lines
- Method shouldn’t be > 5 lines
- Methods shouldn’t take >4 parameters (each hash option is 1 parameter)
- Controllers can instantiate only one object. Views can only know about one instance variable, and views should only send messages to that object.
Day 20: Code Retreat
Monday gave us a chance to put some of these principles into practice: after a quick discussion on modules (the basics: classes in Ruby can only inherit from one other class. Using modules (mixins) lets us pull in different sets of behaviors/methods and make them available to the class), we spent the rest of the day on a single problem: Conway’s Game of Life. The twist: every 15-45 minutes, we had to wipe the slate clean and start over with a different partner and a different set of constraints. These were the rules:
- “caveman coder”: We were only allowed a dry erase market and a section of whiteboard.
- “navigator-driver”: one person types, the other person directs
- “silent”: can only communicate through comments in the code
- “flat files”: write methods so things aren’t overly nested
- “sandi’s rules”: no classes > 100 lines, no methods > 5 lines, etc.
- “many to one”: one driver, everyone else in the class is navigating
- “git happens”: we were forced to run
git reset --hard HEADat frequent, random intervals
- “hot potato”: in a team of four, switch who’s working on the code every 5 minutes
This was both incredibly fun and incredibly frustrating. My takeaways: I like to start with constraints: the board size, the edge case cells. After watching a screencast on using TDD to approach Conway’s Game of Life and talking to others in class, I realized that some people prefer to start with smallest unconstrained piece, ignore the constraints as long as possible, and build from there. This is a totally foreign way of thinking for me, and I’m still struggling to wrap my head around it—even a week later, as I try to process it, my brain is shrieking “BUT…. BUT…. BUT!!!!” Being exposed to this way of thinking and having to pair program with people who tackle problems this way was good for me, though, and I’m hoping to bring a bit more of this kind of thinking into my work, if only as a lightweight experiment at first, and see how it affects my code.
— RebekahHeacockJones (@rebekahredux) September 20, 2016
I’m SO EXCITED to start learning Rails. I’ve used (and am TA-ing for a class on) Laravel, and every time I tell anyone that, I feel like the response is always about how much better Rails is. A few points of comparison so far:
- The list of ‘uncountables’ in Laravel has long been one of my favorite things (see, e.g., bison, coreopsis, emoji, moose, plankton, pokemon, swine). To be honest, I’m finding the Rails list (in total: equipment information rice money species series fish sheep jeans police) to be sadly lacking.
- The Rails doctrine is pretty fantastic. I 100% support the idea that “constraints liberate even the most able minds.”
- Rails gives you the ability to set up example data (used for testing) separately from seed data (necessary for your app to function). This division doesn’t exist by default in Laravel.
- Serializers let you control what data you pass back (to exclude timestamps or secret/private information, for example). I haven’t used this in Laravel before (it sounds like serializers come into play when you’re building APIs), but the functionality exists.
- It sounds like I won’t get the chance to compare views in Rails/Laravel—GA only teaches Rails as a way to create APIs (all the front-end work is done as single-page applications).
Before we got too far into Rails, though, we had to learn about PostgreSQL. So far: no real opinions on this vs MySQL, though I am amused by PostgreSQL’s claim to be “the world’s most advanced open source database” compared to MySQL’s “most popular open source database.”
It was good to brush up on my SQL and to play around with psql (a client for working with Postgres), mostly because I can’t wait to start working with data models that I get to design. Front-end is fun, but back-end makes me feel powerful.
Wednesday kicked off a several-day process of building three different basic APIs: a library API with books and authors, a clinic API with patients and doctors, and a cookbooks API with recipes and ingredients. We learned how to get a Rails app up and running; how to create controllers, models, migrations, and serializers (and how to use
scaffold to create all four at once), how to use
rails console to interact with our apps through
pry, and how to use
rails db to interact with the database using
psql. A few related notes/tidbits:
- Sometimes Rails servers don’t close all the way, which prevents you from opening up a new one. To fix: open Activity Monitor and look for spring, ruby, and rails. Kill anything related to those three terms, then try again.
- Running commands using
bundle execuses the versions you have stored in your gem file instead of global versions.
- In SQL/Postgres, foreign keys don’t get indices by default. Indices make querying fast. If you’re using an id in a query, write an index. (When you set up references in Rails, indices appear to be automatically created.)
- SQL Joins: inner joins between tables A and B cover the intersection of data between the two tables. Left joins cover all of table A, plus additional data from table B that meet the join condition. Right joins cover all of table B, plus additional data from table A that meet the join condition. Full joins give you everything.