We all recognize that we should be writing semantic code. Maybe, you're even using <section>
or <em>
correctly, and feel pretty good about yourself. But, are you also considering the implied contract that exists when you code?
Let's imagine that a customer requests a text link, “See More,” which should reveal additional text on the page. <a href="#">see more</a>
with a click handler should work perfectly, right? Hey, it looks and functions as requested!
No, it will not always work correctly, as it violates the contract between you and the browser. In particular, I'm referring to the one that says the href
attribute must have a value that is a valid URL.
There are practical problems which can arise when violating contracts, and those are much better reasons to write semantic code than things you usually think of when you hear the term, “semantic”.
Popular Arguments for Semantic Code
There are, however, much more practical reasons to care about semantics: contracts.
If you ask an average developer what the value of semantic code, you'll likely hear something along the lines of:
- It assists the disabled
- Properly describing code makes it easier for machines to interpret
Both are, unfortunately, very easy to neglect. “Blind people and robots are not the target audience” is too simple of a response, regardless of how misguided or ignorant it might be.
In other cases, you might even hear cyclical reasoning, such as “non-semantic code is bad, because it is not meaningful.” It is such a popular notion to the point that parodies on it have already sprung up around the web!
There are, however, much more practical reasons to care about semantics: contracts.
Usage Contracts
Any time that you use some functionality provided by browser vendors, your programming language, or an API, you are relying on a contract.
On one side of the contract exists a provider of functionality. For example, when you use an <a>
tag, browser developers are promising to you that they will provide an easy way for the user of your application to navigate to the specified URL.
As always, however, there's the other side of that contract. The implementer of the functionality promises to use the functionality as specified. As soon as he misuses this functionality, all bets are off - leading to potential failure.
Browser functionality contracts
Let's return to the “See More” example from earlier. In the early days of JavaScript, developers would write their JavaScript using a pseudo protocol, javascript
:
<a href="javascript:ShowMore()">See More</a>
Relatively quickly, they began to realize that this approach has at least one practical flaw: the JavaScript code is displayed in the status bar of the browser, which doesn't look professional. The answer to this dilemma was to move the code to an onclick
handler, and replace the href
attribute with an empty hash identifier, like so:
<a href="#" onclick="ShowMore();return false;">See More</a>
Of course, this didn't solve everything. If ShowMore
has an error, return false
is never called, and the page is scrolled to the top - certainly not desired behavior.
Today, developers separate their HTML and JavaScript and use proper event prevention techniques:
<a href="#" class="show-more">See More</a>
$('a.show-more').on('click', function(event) { event.preventDefault(); ShowMore(); });
But guess what? There are still plenty of problems with this approach: opening in a new tab, bookmarking or copying the link does not work as the user might expect (especially, if there is a <base>
tag on the page), which results in confusion, and possibly, a lost customer.
Notice how all the problems described so far arose from a single fact. Developers have been violating their part of the contract with the browser makers: href
attribute must contain a correct URL, but, instead, developers were inserting all kinds of garbage.
If we try to conform to our contract, we must ask ourselves: "Why does the contract not allow us to do what we need?" The answer is simple: we misuse the <a>
tag. We only use it, because we want the text “See More” to appear and behave like a link. However, when we use words, such as "look" and "appear," is this not the domain of CSS? Isn't this "See More" link, in reality, just a button? This is easy to implement:
<button type="button" class="link-style show-more">See More</button>
$('button.show-more').on('click', ShowMore);
a, .link-style { color: @your-shade-of-blue; text-decoration: underline; } .link-style { font: inherit; background: none; border: none; cursor: pointer; }
Regardless of how many new ways to interact with links are added to future iterations of the browsers, this approach will continue to work, because we are not violating any contract with the browser. We are using elements to convey their meaning, and, instead of constantly working around new problems, we know that the browser will treat this correctly, as it understands what we want.
Sure, it might require a few extra lines of CSS to reset some of the default button styling. It's worth it! Don't be lazy.
Specifications as contracts
...those who did know the semantics, and knowingly ignored them.
In 2005, a large portion of the web development community learned the hard way that ignoring HTTP requirements is bad. The newly released Google Web Accelerator prefetches URLs on a page to minimize the user’s waiting time on a click.
This wrecked havoc in applications, which ignored HTTP, and put destructive operations behind simple links. And there were many such applications.
The problem did not rest in the hands of the developers who didn't understand the semantics of HTTP methods. No, the problem came from those who did know the semantics, knowingly ignored them, and did not share the knowledge.
Maintenance Contracts
Confusing code is bad!
Contracts also exist between developers. Every time you write a line of code, you make a promise to future developers that it does exactly what it appears to do. In other words, confusing code is bad!
For example, using a filtering function (such as filter
in Python) to loop over items is incorrect, because there is an implied contract that filtering functions only modify the list, without having any side effects:
def show(item): print(item) fruits = ['orange', 'apple', 'lemon'] filter(show, fruits) # Incorrect usage of filter
Instead, use explicit looping over a list:
def show(item): print(item) fruits = ['orange', 'apple', 'lemon'] for fruit in fruits: show(fruit)
On the other hand, if you can rely on a contract, it, as a result, allows you to clarify your code. For instance, using array_filter
instead of a for
loop in PHP to filter items allows other developers to understand what is happening after a single glance:
$incomes = [20, 15, -7, 19]; $profits = array_filter($incomes, function($i){ return $i > 0; });
Compare the snippet above to using a for
loop:
$incomes = [20, 15, -7, 19]; $profits = []; foreach ($incomes as $i) { if ($i > 0) { $profits[] = $i; } }
The second version is worse, not only because it is longer, but also because array_filter
provides an expectation. It is clear that $profits
is a subset of $incomes
. There is no such convention with a generic for
loop, which explains why we can't make any assumptions, without first deciphering the internals of the loop.
Writing Properly Semantic Code
How do you distinguish between code that is and isn't semantic? How do you notice these contracts? For the most part, simply asking yourself questions should suffice.
HTML Tags
“I am using an
<a>
tag and I need to put something in anhref
attribute.href
stores the link target destination. So where does my<a>
tag link to?” Nowhere? Well, I can then conclude that I am probably misusing this tag.
Named Functions
“I need to print out the contents of my list in a
filter
function. Is printing out items part of the filtering process?” Not really. That means I am misusingfilter
.
Functions That Do Too Much
“I am writing a
getFoo
method, and need to add some modification code within it. Does modifying state make sense as part of getting foo? Would anyone who simply wants to get foo expect that something else happens?” Likely not!
Separation of Concerns
“I want to add some JavaScript within an HTML
onclick
attribute. Is this correct?” The file you are modifying is.html
, right? Instead, place your JavaScript and CSS within.js
and.css
files, respectively. Always.
Of course, in many cases, some additional knowledge might be required to properly assess the situation. What matters most is to begin thinking about these things, and to avoid the principle “it (kinda) works, so it is fine enough.” Instead, always ask yourself, "What does the code I am writing actually mean?"
Conclusion
Violating a contract ensures that cooperation will break.
Cooperation always depends on effort from both parties. Most of software development reduces to cooperation: cooperation between hardware manufacturers, system programmers, browser developers, library authors, website developers, and many others. Violating a contract ensures that cooperation will break.
Code semantics is one such contract. Care about it for your own good!
Footnotes
Semantic: The term “semantic” is often misunderstood. In the context of this article, I use the term to distinguish the meaning of the code from the results the code produces. For example, semantic meaning of an <a>
tag is representation of a link, while the simple, practical meaning of it is clickable, underlined text.
Strictly speaking, an empty fragment identifier #
might be considered valid in this context. This technicality, however, misses the point I make in the article.
Comments