Occam’s Razor and Brushes
Alex Gorbatchev’s SyntaxHighlighter uses separate modules (Brushes) to process the syntax for individual programming languages. A web page that uses SyntaxHighlighter must load the main JavaScript file (shCore.js), and load the required brush files. After the brushes are loaded, the page sets any desired configuration parameters for the highlighter, then invokes SyntaxHighlighter.all
, which performs the formatting and highlighting.
SyntaxHighlighter currently includes 21 brushes, each in its own JavaScript file. A web page author may wish to load only those brush files that are needed for a given page. David Chambers addressed this by using JavaScript to request the needed brushes from the server. The first time that I browsed his site, using Firefox, this worked well. But the second time, using Internet Explorer, an error message popped up in my browser window:
David’s script worked by extending the page’s <head>
with a new <script>
element to load the brushes. Firefox executed the new script as soon as it was added, but IE delayed execution of the new script until the first one was finished. That first script went on to call SyntaxHighlighter.all()
. Because the new script hadn’t been run, the brushes hadn’t been loaded yet; this resulted in the error that I saw.
David resolved this problem by modifying his scripts, but I got to wondering: Was there a way to remove that ordering dependency, so that the brushes didn’t need to be loaded first? I aimed to make this work by splitting the highlighting into two phases:
- When the web page calls
SyntaxHighlighter.all()
, SyntaxHighlighter formats any code whose brushes have already been loaded. This is like its previous design, but when it can’t find a brush, it no longer reports that failure as an error. - If a brush is loaded after the call to
SyntaxHighlighter.all()
, then SyntaxHighlighter makes another pass through the document, looking for any code that needs to be formatted using the newly-loaded brush.
If SyntaxHighlighter can make use of each brush as it’s loaded, then there’s no need for the web page to synchronize the brush load operations. This would have simplified David’s task considerably. Unfortunately, SyntaxHighlighter had no way of knowing when a new brush has been loaded.
The structure of a SyntaxHighlighter brush, as currently written, corresponds to the boilerplate code shown below. (The /*
comments */
indicate where the contents differ for each brush.)
SyntaxHighlighter.brushes./*languageName*/ = function() { /* This function determines the styles for a particular syntax */ }; SyntaxHighlighter.brushes./*languageName*/.prototype = new SyntaxHighlighter.Highlighter(); SyntaxHighlighter.brushes./*languageName*/.aliases = [/* An array of alternate names for this language */ ];
Here’s the brush structure as I’ve redesigned it, with most of the boilerplate now encapsulated in a new registration method, SyntaxHighlighter.registerBrush
:
SyntaxHighlighter.registerBrush( "/*languageName*/", [/* An array of alternate names for this language */ ], function() { /* This function determines the styles for a particular syntax */ });
The new method, SyntaxHighlighter.registerBrush
, attempts to apply the brush as part of the registration process, through an internal call to the method that performs the highlighting. This call returns immediately if the web page hasn’t yet invoked SyntaxHighlighter.all()
, because it can’t assume that the configuration is ready. (Any pending highlighting will be performed when SyntaxHighlighter.all()
is called.) Otherwise, the newly-registered brush is applied to any code blocks that require that brush.
This encapsulation benefits everyone involved: The brush code now needs less knowledge of the highlighter’s internals, and assumes less responsibility for them. SyntaxHighlighter is given knowledge that it didn’t previously have: namely, it gets notified as brushes become available. And the web page doesn’t need any additional logic to orchestrate the brush loading.
Occam’s Razor is often paraphrased as “The simplest explanation is best.” Ironically, that’s a little too simplistic. The original Latin can be translated into English as, entities must not be multiplied beyond necessity. In software engineering, Occam’s Razor has a corollary: When entities are multiplying, find out how they became necessary. Doing so may lead to a better simplicity.
JavaScript, Software Development, syntaxhighlighter
While I’ve written code to work in tandem with SyntaxHighlighter, your willingness to perform surgery on SyntaxHighlighter itself appears to have once again achieved the optimal solution. Nice one, Dan!