It sounds like incredibly simple advice. Pick a way to do things (preferably a simple, sensible way), and then keep doing them that way. But in fact, “being consistent” is one of the most challenging parts of being a front-end developer for one simple reason — consistency requires discipline.
At first glance, the Shapeways New York office doesn’t come across as a pillar of regulation. “Fun” is a Shapeways core value. The company strives to maximize creativity, enjoyment, and happiness, and through those avenues, productivity and quality of work. Discipline is no longer about suits, or early mornings, or dictatorial management. It’s not about being punctual, or eating your broccoli, or going to the gym because it’s good for you. Discipline at Shapeways is, instead, about how we write code. It’s about making sure the code we write follows the same patterns as the code everybody else is writing. Consistency in a codebase relies entirely, after all, on the discipline of the engineers who wrote it.
Disciplined programming is, however, difficult at the best of times, and without a support structure, nearly impossible. We rely on architectural structure to alleviate some of the daily frustrations that come with programming, to minimize productivity blockers, and to help us to focus on what we’re actually building. This is why, at Shapeways, the front-end development team has weekly meetings devoted just to architecture. We want to make sure everyone is on board when we pick (what we think is) a simple, sensible way of doing things, so that everyone can do things the same way. We also want to minimize the burden of discipline, and find ways to make it as easy as possible to write consistent code.
Done might be better than perfect, but done is not better than good.
The engineering goals of most Shapeways front-end patterns are fairly standard. We want:
- The ability to find, understand, and expand on old work as quickly as possible
- Separation of concerns, encapsulation, modularity, and reusability.
- Standardized documentation
- Evolution and implementation of new ideas with minimum headaches for ourselves and minimum disruptions to our codebase
We also want to make following patterns that achieve our engineering goals as efficient and natural as possible. What we don’t want are bloated files, tight coupling, and hacky unique snowflake functions that live in strange corners of our templates. It is with these aims in mind that we have structured our PHP View Components.
The idea that lies behind Shapeways View Components is fairly simple; most sections of the Shapeways website, be they 3D modeling tools or part of the marketplace, are built of complex interactive pieces. Such pieces should, ideally, be encapsulated so that all of their logic is built and maintained in a single location. They should then be injected into different parts of the site by reference, so that code doesn’t get duplicated.
One example currently on Shapeways is the “order again” feature, which allows a customer to place the entire contents of an order back into his or her cart. The feature needs to:
- Pull order information from our database
- Update the cart to reflect the added items
- Render a useful error message if any item is no longer available for purchase
The feature currently appears for each order on the order history page, and each individual order status page, though these locations may change in the future. The issues with keeping a unique version of the feature in each place quickly become evident. Maintenance, updates, and alterations would take on nightmarish proportions. Changes to the appearance or copy of the feature in one location would not necessarily propagate across other pages, leading to an inconsistent experience. Bugs would become harder to track down. Pages that contained the feature would become bloated. Everyone on the team would feel vague disgust at the sight of repeated code. The list goes on.
Enter View Components. Each component in the Shapeways codebase consists of a PHP object that extends our ShapewaysComponent class, and a markup template. The component object itself handles all internal logic and functionality, and generates a ComponentViewContext which stores information to be passed to the component’s template for rendering the component on the actual webpage. The ViewContext contains a set of private viewData variables that are accessed using class methods. With View Components, rather than reinventing the wheel every time we want to add the “order again” feature to a new page, we can simply inject it into our existing structure with a single line of code and move on. If we want to update the icon associated with the feature, or alter the implementation of any portion of it, we can do so in a single location. This system neatly enforces abstraction barriers, encapsulation, and logic bundling, allowing us to reduce our codebase and make it easier to use, maintain, and improve on.
To generate new components, we use a script that creates and correctly places the component’s class and template skeletons in our file structure. The script populates the basic component constructor, links the component class with its markup template, and generates the associated ComponentViewContext to go between the two. This script has several key benefits:
- Minimum habit disruption: it’s easy to update the component skeleton without drastically changing the flow of template generation
- Basic functionality: by populating the most essential functions, the script provides a guide for what all a component should ideally contain
- Headache reduction: by generating the skeleton and placing it in our file structure correctly, the script allows us to focus on building the features represented by our components, rather than on our components themselves
But most importantly, our constructor script, and our entire View Component pattern, brings us closer to our goal of architectural consistency. By ensuring that every new component is structured the same way, we make it easier to achieve all of our engineering goals, and by automating their structure, we place the burden of discipline on our codebase rather than on our engineers.
What’s in a Name?
Another, more semantic architectural shift we’re implementing in Shapeways’s front-end is BEM CSS methodology. We use SASS for our stylesheets at Shapeways, and while the benefits of being able to use variable names, nesting and mixins are many, SASS (and in fact any kind of CSS) becomes unmanageably very quickly. Although CSS is a style sheet language, all of our front-end engineering goals can, and indeed must, be applied to our SASS structure as much as to any other part of the Shapeways codebase. We have chosen to work towards those goals through BEM. BEM, which stands for Block, Element, Modifier, is more of a paradigm shift than a framework in the traditional sense. Its structure relies on naming and organizational convention. It provides a standardized way to name new CSS attributes, and a system for organizing them into modules.
A BEM “block” is defined by the BEM methodology documentation as an independent entity or “building block” of an application, for example “search” or “navigation”. Each block behaves independently of other blocks in the application. An “element” of a block is a functional piece of the block, for example the search input and button, or the separate menu items in the navigation bar. A “modifier” is a variation of a block or element, for example a color change on a button. Blocks, along with their elements and modifiers, are then bundled into modules, which can be applied to any section of our site.
The key to BEM is that each block, element, and modifier is denoted not by something like nested SASS, but rather by class names that follow a standardized naming convention. At Shapeways, we use two underscores (“__”) to denote an element, and two dashes (“ — ”) to denote a modifier. Using this standardized naming structure, and by placing our SASS partials into a library of generic modules, we make it easier and more efficient to locate the style rules we need and apply them, without having to recreate the same CSS over and over again. Furthermore, because this system integrates directly into HTML markup, it’s easier to migrate to gradually, and allows us to roll out modules and improvements without having to cannibalize our entire SASS library all at once. The end result is drastically improved consistency, modularity and reusability, with minimum requirements of discipline from our engineers. As long as everyone on the team simply adheres to the BEM naming structure, style rules are easy to locate and use, changes can be implemented in a central location, and all of our markup styling logic follows a consistent pattern
The Road Ahead
In the end, being consistent is still not as simple as we would like. We continue to strive, toil and iterate on our systems, our patterns, and our habits as developers. We will continue to search for new ways to make disciplined programming easier, and to apply our engineering goals to our codebase. After all, the great success of continuously deployed web development is that sooner or later, someone is going to come back and improve on what we build, and to be good software engineers and good team players, we have to make sure it’s ready when they do.