{"id":5898,"date":"2020-10-30T08:00:21","date_gmt":"2020-10-30T08:00:21","guid":{"rendered":"https:\/\/cloudfour.com\/?p=5898"},"modified":"2022-08-30T11:16:36","modified_gmt":"2022-08-30T18:16:36","slug":"perfectly-broken-code","status":"publish","type":"post","link":"https:\/\/cloudfour.com\/thinks\/perfectly-broken-code\/","title":{"rendered":"Perfectly Broken Code"},"content":{"rendered":"<div class=\"u-pullSides1\">\n  <img src=\"https:\/\/cloudfour.com\/wp-content\/uploads\/2020\/10\/perfectly-broken-code-preview-2.png\" alt=\"A cute, smiling bug between two code brackets\"><\/div>\n<p>Naturally, as a Senior Front-End Developer with over a decade of experience, the code that I write is flawless and bug-free all day, every day. LOL \u2014&nbsp;<em>Just kidding.<\/em>&nbsp;\ud83d\ude05 \ud83e\udd84<\/p>\n<p>Even though I have many years of experience, I write code that breaks every single day. There\u2019s no magical transformation from when I first started writing code to today. I&#8217;ve realized and learned that writing broken code is part of the process; it\u2019s all about iteration. With the experience I\u2019ve gained, I do see patterns quicker. But I still make mistakes, especially in the first iteration.<\/p>\n<p>Join me in exploring a recent experience where I started with flawed logic (without realizing it) and the steps I took to fix my bug. Let&#8217;s experience some broken code together. \ud83c\udf89<\/p>\n<h2>The goal<\/h2>\n<p>My goal is to write a JavaScript utility function, <code>getRenderOptionsFromEl()<\/code>, that reads specific <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTML\/Global_attributes\/data-*\">custom data attributes<\/a> to create and return a JavaScript object literal with boolean values.<\/p>\n<p>From this source HTML:<\/p>\n<pre aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"widget js-widget\"<\/span> \n  <span class=\"hljs-attr\">data-widget-id<\/span>=<span class=\"hljs-string\">\"123\"<\/span> \n  <span class=\"hljs-attr\">data-render-call-to-action-button<\/span>=<span class=\"hljs-string\">\"true\"<\/span>\n  <span class=\"hljs-attr\">data-render-flags<\/span>=<span class=\"hljs-string\">\"false\"<\/span> \n  <span class=\"hljs-attr\">data-render-logos<\/span>=<span class=\"hljs-string\">\"false\"<\/span> \n  <span class=\"hljs-attr\">data-render-title<\/span>=<span class=\"hljs-string\">\"false\"<\/span> \n  <span class=\"hljs-attr\">data-other<\/span>=<span class=\"hljs-string\">\"Some other data\"<\/span> \n&gt;<\/span>\n  <span class=\"hljs-comment\">&lt;!-- Widget HTML --&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>I want to generate a JavaScript object literal with <em>only<\/em> the <code>render*<\/code> properties and values as booleans:<\/p>\n<pre aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">{\n  <span class=\"hljs-attr\">renderCallToActionButton<\/span>: <span class=\"hljs-literal\">true<\/span>,\n  <span class=\"hljs-attr\">renderFlags<\/span>: <span class=\"hljs-literal\">false<\/span>,\n  <span class=\"hljs-attr\">renderLogos<\/span>: <span class=\"hljs-literal\">false<\/span>,\n  <span class=\"hljs-attr\">renderTitle<\/span>: <span class=\"hljs-literal\">false<\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>Requirements:<\/h3>\n<ol>\n<li>All custom data attributes will start with <code>data-render-*<\/code> as a naming convention.<\/li>\n<li>The following <code>data-render-*<\/code> attributes can optionally exist for a given widget:\n<ul>\n<li><code>data-render-call-to-action-button<\/code><\/li>\n<li><code>data-render-flags<\/code><\/li>\n<li><code>data-render-logos<\/code><\/li>\n<li><code>data-render-title<\/code><\/li>\n<\/ul>\n<\/li>\n<li>For each <code>data-render-*<\/code> attribute, its value will either be a string of <code>\"true\"<\/code> or <code>\"false\"<\/code>.<\/li>\n<li>There may be one or more widgets on the page.<\/li>\n<li>The <code>getRenderOptionsFromEl()<\/code> function should accept an <code>HTMLElement<\/code> argument and return an object literal with boolean values assigned to the <code>render*<\/code> properties.<\/li>\n<\/ol>\n<h2>Solution one: I thought it was working<\/h2>\n<p>Assuming the source HTML from above, I can access the <code>data-render-logos=\"false\"<\/code> data attribute directly using the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Learn\/HTML\/Howto\/Use_data_attributes#JavaScript_access\"><code>dataset<\/code> read-only property<\/a>:<\/p>\n<pre aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">const<\/span> el = <span class=\"hljs-built_in\">document<\/span>.querySelector(<span class=\"hljs-string\">'.js-widget'<\/span>);\n\n<span class=\"hljs-keyword\">if<\/span> (el) {\n  <span class=\"hljs-comment\">\/\/ The dataset dot-notation is so smooth...\ud83d\ude0e<\/span>\n  <span class=\"hljs-keyword\">const<\/span> shouldRenderLogos = el.dataset.renderLogos; \n\n  <span class=\"hljs-built_in\">console<\/span>.log(shouldRenderLogos); <span class=\"hljs-comment\">\/\/ \"false\"<\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p><em>Sweet!<\/em> Following this approach, the first implementation of the utility function was as follows:<\/p>\n<pre aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ scripts\/utils\/get-render-opts-from-el.js<\/span>\n\n<span class=\"hljs-comment\">\/**\n * Generates an object literal with render option properties\n * \n * <span class=\"hljs-doctag\">@param <span class=\"hljs-type\">{HTMLElement}<\/span> <\/span>el The element to read data attributes from\n * <span class=\"hljs-doctag\">@returns <span class=\"hljs-type\">{Object}<\/span> <\/span>An object of boolean render options\n *\/<\/span>\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> getRenderOptionsFromEl = <span class=\"hljs-function\"><span class=\"hljs-params\">el<\/span> =&gt;<\/span> ({\n  <span class=\"hljs-comment\">\/\/ For each property, set its value to a boolean by <\/span>\n  <span class=\"hljs-comment\">\/\/ checking if the dataset property is 'true'.<\/span>\n  <span class=\"hljs-attr\">renderCallToActionButton<\/span>: el.dataset.renderCallToActionButton === <span class=\"hljs-string\">'true'<\/span>,\n  <span class=\"hljs-attr\">renderFlags<\/span>: el.dataset.renderFlags === <span class=\"hljs-string\">'true'<\/span>,\n  <span class=\"hljs-attr\">renderLogos<\/span>: el.dataset.renderLogos === <span class=\"hljs-string\">'true'<\/span>,\n  <span class=\"hljs-attr\">renderTitle<\/span>: el.dataset.renderTitle === <span class=\"hljs-string\">'true'<\/span>\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Now that we have the utility function, let&#8217;s test it out against an HTML element on the page:<\/p>\n<pre aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-comment\">&lt;!-- index.html --&gt;<\/span>\n\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>\n  <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"widget js-widget\"<\/span>\n  <span class=\"hljs-attr\">data-widget-id<\/span>=<span class=\"hljs-string\">\"123\"<\/span>\n  <span class=\"hljs-attr\">data-render-call-to-action-button<\/span>=<span class=\"hljs-string\">\"true\"<\/span>\n  <span class=\"hljs-attr\">data-render-flags<\/span>=<span class=\"hljs-string\">\"false\"<\/span>\n  <span class=\"hljs-attr\">data-render-logos<\/span>=<span class=\"hljs-string\">\"false\"<\/span>\n  <span class=\"hljs-attr\">data-render-title<\/span>=<span class=\"hljs-string\">\"false\"<\/span>\n  <span class=\"hljs-attr\">data-other<\/span>=<span class=\"hljs-string\">\"Some other data\"<\/span> \n&gt;<\/span>\n  <span class=\"hljs-comment\">&lt;!-- Widget HTML --&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n<span class=\"hljs-comment\">&lt;!-- \n  Modern browsers support JavaScript modules natively\n  @see https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Guide\/Modules#Applying_the_module_to_your_HTML\n  @see https:\/\/caniuse.com\/#feat=es6-module\n--&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">script<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"module\"<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"scripts\/index.js\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">script<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>And here&#8217;s the <code>scripts\/index.js<\/code> JavaScript file that imports and uses the utility function:<\/p>\n<pre aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ scripts\/index.js<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> { getRenderOptionsFromEl } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/utils\/get-render-opts-from-el'<\/span>;\n\n<span class=\"hljs-built_in\">document<\/span>.querySelectorAll(<span class=\"hljs-string\">'.js-widget'<\/span>).forEach(<span class=\"hljs-function\"><span class=\"hljs-params\">el<\/span> =&gt;<\/span> {\n  <span class=\"hljs-comment\">\/\/ Get the render options for each widget<\/span>\n  <span class=\"hljs-keyword\">const<\/span> renderOpts = getRenderOptionsFromEl(el);\n\n  <span class=\"hljs-comment\">\/\/ Let's see what the renderOpts object looks like!<\/span>\n  <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">`Widget <span class=\"hljs-subst\">${el.dataset.widgetId}<\/span>`<\/span>, renderOpts);\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Which logs out to the browser console:<\/p>\n<pre aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ browser console<\/span>\n\nWidget <span class=\"hljs-number\">123<\/span>\n{\n  <span class=\"hljs-attr\">renderCallToActionButton<\/span>: <span class=\"hljs-literal\">true<\/span>, \n  <span class=\"hljs-attr\">renderFlags<\/span>: <span class=\"hljs-literal\">false<\/span>, \n  <span class=\"hljs-attr\">renderLogos<\/span>: <span class=\"hljs-literal\">false<\/span>, \n  <span class=\"hljs-attr\">renderTitle<\/span>: <span class=\"hljs-literal\">false<\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Yay, it worked! Goal accomplished. \ud83c\udf89 You can see it <a href=\"https:\/\/4opb9.csb.app\/\">running in the browser<\/a> as well:<\/p>\n<div class=\"u-release\">\n<div class=\"u-containSpread\">\n  <a href=\"https:\/\/4opb9.csb.app\/\"><img src=\"https:\/\/cloudfour.com\/wp-content\/uploads\/2020\/10\/solution-1-browser-screenshot.png\" alt=\"A browser screenshot with the console log showing expected output for solution one\"><\/a><\/div>\n<\/div>\n<h2>Time to add multiple widgets<\/h2>\n<p>Seeing the logic works as designed, I continue by adding a second widget onto the page representing a second use case, a widget with a limited set of custom <code>data-render-*<\/code> attributes. The updated source HTML looks as follows:<\/p>\n<pre aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-comment\">&lt;!-- index.html --&gt;<\/span>\n\n<span class=\"hljs-comment\">&lt;!-- First widget with all four render data attributes --&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>\n  <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"widget js-widget\"<\/span>\n  <span class=\"hljs-attr\">data-widget-id<\/span>=<span class=\"hljs-string\">\"123\"<\/span>\n  <span class=\"hljs-attr\">data-render-call-to-action-button<\/span>=<span class=\"hljs-string\">\"true\"<\/span>\n  <span class=\"hljs-attr\">data-render-flags<\/span>=<span class=\"hljs-string\">\"false\"<\/span>\n  <span class=\"hljs-attr\">data-render-logos<\/span>=<span class=\"hljs-string\">\"false\"<\/span>\n  <span class=\"hljs-attr\">data-render-title<\/span>=<span class=\"hljs-string\">\"false\"<\/span>\n  <span class=\"hljs-attr\">data-other<\/span>=<span class=\"hljs-string\">\"Some other data\"<\/span> \n&gt;<\/span>\n  <span class=\"hljs-comment\">&lt;!-- Widget 123 HTML --&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n<span class=\"hljs-comment\">&lt;!-- Second widget with only two render data attributes --&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>\n  <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"widget js-widget\"<\/span>\n  <span class=\"hljs-attr\">data-widget-id<\/span>=<span class=\"hljs-string\">\"456\"<\/span>\n  <span class=\"hljs-attr\">data-render-flags<\/span>=<span class=\"hljs-string\">\"false\"<\/span>\n  <span class=\"hljs-attr\">data-render-logos<\/span>=<span class=\"hljs-string\">\"false\"<\/span>\n  <span class=\"hljs-attr\">data-are-you-having-fun<\/span>=<span class=\"hljs-string\">\"Absolutely!\"<\/span>\n&gt;<\/span>\n  <span class=\"hljs-comment\">&lt;!-- Widget 456 HTML --&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Taking a peek at the browser console, I see the following:<\/p>\n<pre aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ browser console<\/span>\n\nWidget <span class=\"hljs-number\">123<\/span> \n{\n  <span class=\"hljs-attr\">renderCallToActionButton<\/span>: <span class=\"hljs-literal\">true<\/span>, \n  <span class=\"hljs-attr\">renderFlags<\/span>: <span class=\"hljs-literal\">false<\/span>, \n  <span class=\"hljs-attr\">renderLogos<\/span>: <span class=\"hljs-literal\">false<\/span>, \n  <span class=\"hljs-attr\">renderTitle<\/span>: <span class=\"hljs-literal\">false<\/span>\n}\n\nWidget <span class=\"hljs-number\">456<\/span> \n{\n  <span class=\"hljs-attr\">renderCallToActionButton<\/span>: <span class=\"hljs-literal\">false<\/span>, \n  <span class=\"hljs-attr\">renderFlags<\/span>: <span class=\"hljs-literal\">false<\/span>, \n  <span class=\"hljs-attr\">renderLogos<\/span>: <span class=\"hljs-literal\">false<\/span>, \n  <span class=\"hljs-attr\">renderTitle<\/span>: <span class=\"hljs-literal\">false<\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Oh no, there is a bug! \ud83d\ude31<\/p>\n<p>Notice the second widget, <strong>Widget 456<\/strong>, only has <em>two<\/em> custom <code>data-render-*<\/code> attributes in the source HTML:<\/p>\n<ul>\n<li><code>data-render-flags=\"false\"<\/code><\/li>\n<li><code>data-render-logos=\"false\"<\/code><\/li>\n<\/ul>\n<p>This means the expected output in the browser console for the second widget should be:<\/p>\n<pre aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">Widget <span class=\"hljs-number\">456<\/span> \n{\n  <span class=\"hljs-attr\">renderFlags<\/span>: <span class=\"hljs-literal\">false<\/span>, \n  <span class=\"hljs-attr\">renderLogos<\/span>: <span class=\"hljs-literal\">false<\/span>, \n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h2>Broken code represents opportunities<\/h2>\n<p>The first iteration of the utility function logic is flawed. Where do I go from here? Well, I fix it, of course. Before doing so, I see a great opportunity at this point in the process of adding some test assertions. Before this, I didn&#8217;t write a test, and that was okay. The logic seemed pretty straightforward, and 100% code coverage isn&#8217;t the end-all-be-all. Tests should add more confidence that your code&#8217;s logic will work as expected, and I believe that <a href=\"https:\/\/kentcdodds.com\/blog\/how-to-know-what-to-test\">testing for use cases provides greater confidence<\/a>. Since the logic failed when I put it through the second use case, I&#8217;ve lost confidence. Time to boost my confidence!<\/p>\n<p>Using <a href=\"https:\/\/jestjs.io\/\">Jest<\/a>, I&#8217;ll add a baseline test for the first use case of having only one widget with all four custom <code>data-render-*<\/code> attributes:<\/p>\n<pre aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ scripts\/utils\/get-render-opts-from-el.test.js<\/span>\n\n<span class=\"hljs-comment\">\/\/ Import the utility function we intend to test<\/span>\n<span class=\"hljs-keyword\">import<\/span> { getRenderOptionsFromEl } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/get-render-opts-from-el'<\/span>;\n\ntest(<span class=\"hljs-string\">'getRenderOptionsFromEl() should return four attributes'<\/span>, () =&gt; {\n  <span class=\"hljs-comment\">\/\/ Create a mock element to test against<\/span>\n  <span class=\"hljs-keyword\">const<\/span> el = <span class=\"hljs-built_in\">document<\/span>.createElement(<span class=\"hljs-string\">'div'<\/span>);\n  <span class=\"hljs-comment\">\/\/ Add mock custom data attributes<\/span>\n  el.setAttribute(<span class=\"hljs-string\">'data-widget-id'<\/span>, <span class=\"hljs-string\">'321'<\/span>);\n  el.setAttribute(<span class=\"hljs-string\">'data-render-call-to-action-button'<\/span>, <span class=\"hljs-string\">'true'<\/span>);\n  el.setAttribute(<span class=\"hljs-string\">'data-render-flags'<\/span>, <span class=\"hljs-string\">'false'<\/span>);\n  el.setAttribute(<span class=\"hljs-string\">'data-render-logos'<\/span>, <span class=\"hljs-string\">'false'<\/span>);\n  el.setAttribute(<span class=\"hljs-string\">'data-render-title'<\/span>, <span class=\"hljs-string\">'true'<\/span>);\n  <span class=\"hljs-comment\">\/\/ I add \"other\" custom data attributes to confirm <\/span>\n  <span class=\"hljs-comment\">\/\/ they are ignored by the utility function.<\/span>\n  el.setAttribute(<span class=\"hljs-string\">'data-other'<\/span>, <span class=\"hljs-string\">'Some other value'<\/span>);\n\n  <span class=\"hljs-comment\">\/\/ Use the utility function!<\/span>\n  <span class=\"hljs-keyword\">const<\/span> result = getRenderOptionsFromEl(el);\n  <span class=\"hljs-comment\">\/\/ Set the expectations on what the result _should_ be<\/span>\n  <span class=\"hljs-keyword\">const<\/span> expected = {\n    <span class=\"hljs-attr\">renderCallToActionButton<\/span>: <span class=\"hljs-literal\">true<\/span>,\n    <span class=\"hljs-attr\">renderFlags<\/span>: <span class=\"hljs-literal\">false<\/span>,\n    <span class=\"hljs-attr\">renderLogos<\/span>: <span class=\"hljs-literal\">false<\/span>,\n    <span class=\"hljs-attr\">renderTitle<\/span>: <span class=\"hljs-literal\">true<\/span>\n  };\n\n  <span class=\"hljs-comment\">\/\/ Finally, write the assertion<\/span>\n  expect(result).toEqual(expected);\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>The test passes, as expected, which we verified in the browser for this first use case:<br \/>\n<figure class=\"Figure\"><\/p>\n<div class=\"u-pullSides1\">\n  <a href=\"https:\/\/codesandbox.io\/s\/solution-one-i-thought-it-was-working-single-widget-with-first-test-p936i?file=\/scripts\/utils\/get-render-opts-from-el.test.js\">\n<img src=\"https:\/\/cloudfour.com\/wp-content\/uploads\/2020\/10\/solution-1-single-test-passing.png\" alt=\"'getRenderOptionsFromEl() should return four attributes' test passes\">\n<\/a><\/div>\n<p><\/figure><\/p>\n<p>Next, I add a new test for the second use case where the bug was discovered:<\/p>\n<pre aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ scripts\/utils\/get-render-opts-from-el.test.js<\/span>\n\ntest(<span class=\"hljs-string\">'getRenderOptionsFromEl() should only return two attributes'<\/span>, () =&gt; {\n  <span class=\"hljs-comment\">\/\/ Create mock element that only has<\/span>\n  <span class=\"hljs-comment\">\/\/ two data-render-* attributes.<\/span>\n  <span class=\"hljs-keyword\">const<\/span> el = <span class=\"hljs-built_in\">document<\/span>.createElement(<span class=\"hljs-string\">'div'<\/span>);\n  el.setAttribute(<span class=\"hljs-string\">'data-widget-id'<\/span>, <span class=\"hljs-string\">'321'<\/span>);\n  el.setAttribute(<span class=\"hljs-string\">'data-render-call-to-action-button'<\/span>, <span class=\"hljs-string\">'false'<\/span>);\n  el.setAttribute(<span class=\"hljs-string\">'data-render-flags'<\/span>, <span class=\"hljs-string\">'false'<\/span>);\n  el.setAttribute(<span class=\"hljs-string\">'data-other'<\/span>, <span class=\"hljs-string\">'Some other value'<\/span>);\n\n  <span class=\"hljs-keyword\">const<\/span> result = getRenderOptionsFromEl(el);\n  <span class=\"hljs-keyword\">const<\/span> expected = {\n    <span class=\"hljs-attr\">renderCallToActionButton<\/span>: <span class=\"hljs-literal\">false<\/span>,\n    <span class=\"hljs-attr\">renderFlags<\/span>: <span class=\"hljs-literal\">false<\/span>\n  };\n\n  expect(result).toEqual(expected);\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>And our new test fails, confirming the bug:<br \/>\n<figure class=\"Figure\"><\/p>\n<div class=\"u-pullSides1\">\n  <a href=\"https:\/\/codesandbox.io\/s\/solution-one-i-thought-it-was-working-multiple-widgets-jf3uc?file=\/scripts\/utils\/get-render-opts-from-el.test.js\">\n<img src=\"https:\/\/cloudfour.com\/wp-content\/uploads\/2020\/10\/solution-1-tests-failing-1.png\" alt=\"'getRenderOptionsFromEl() should only return two attributes' test fails\">\n<\/a><\/div>\n<p><\/figure><\/p>\n<p>Before we continue, I want to be clear; <strong>a failing test is a good thing!<\/strong> I now have what I need to gain my confidence back in tests to help guide me through the refactor. \ud83d\ude03<\/p>\n<h2>Solution two: The refactor<\/h2>\n<p>With the failing test guiding me, I went ahead and refactored the utility function. I took the opportunity to strengthen the JSDoc <code>@returns<\/code> type by adding a custom <code>RenderOptions<\/code> <a href=\"https:\/\/jsdoc.app\/tags-typedef.html\"><code>@typedef<\/code><\/a> (Type Definition) to help document the new logic:<\/p>\n<pre aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ scripts\/utils\/get-render-opts-from-el.js<\/span>\n\n<span class=\"hljs-comment\">\/**\n * A type definition to provide clear expectations \n * of the object and its properties\n * \n * <span class=\"hljs-doctag\">@typedef <span class=\"hljs-type\">{Object}<\/span> <span class=\"hljs-variable\">RenderOptions<\/span><\/span>\n * \n * All of the render* properties are optional\n *\n * <span class=\"hljs-doctag\">@property <span class=\"hljs-type\">{boolean}<\/span> <\/span>&#091;renderCallToActionButton]\n * <span class=\"hljs-doctag\">@property <span class=\"hljs-type\">{boolean}<\/span> <\/span>&#091;renderFlags]\n * <span class=\"hljs-doctag\">@property <span class=\"hljs-type\">{boolean}<\/span> <\/span>&#091;renderLogos]\n * <span class=\"hljs-doctag\">@property <span class=\"hljs-type\">{boolean}<\/span> <\/span>&#091;renderTitle]\n *\/<\/span>\n\n<span class=\"hljs-comment\">\/**\n * Generates an object literal with render option properties\n *\n * <span class=\"hljs-doctag\">@param <span class=\"hljs-type\">{HTMLElement}<\/span> <span class=\"hljs-variable\">el<\/span><\/span>\n * <span class=\"hljs-doctag\">@returns <span class=\"hljs-type\">{RenderOptions}<\/span> <\/span>\n *\/<\/span>\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> getRenderOptionsFromEl = <span class=\"hljs-function\"><span class=\"hljs-params\">el<\/span> =&gt;<\/span> {\n  <span class=\"hljs-comment\">\/\/ The object that'll eventually be returned<\/span>\n  <span class=\"hljs-keyword\">const<\/span> renderOptions = {};\n\n  <span class=\"hljs-comment\">\/\/ Conditionally add the render* properties only if <\/span>\n  <span class=\"hljs-comment\">\/\/ the data-render-* attribute exists on the DOM element.<\/span>\n\n  <span class=\"hljs-keyword\">if<\/span> (el.dataset.renderCallToActionButton) {\n    <span class=\"hljs-keyword\">const<\/span> value = el.dataset.renderCallToActionButton === <span class=\"hljs-string\">'true'<\/span>;\n    renderOptions.renderCallToActionButton = value;\n  }\n\n  <span class=\"hljs-keyword\">if<\/span> (el.dataset.renderFlags) {\n    <span class=\"hljs-keyword\">const<\/span> value = el.dataset.renderFlags === <span class=\"hljs-string\">'true'<\/span>;\n    renderOptions.renderFlags = value;\n  }\n\n  <span class=\"hljs-keyword\">if<\/span> (el.dataset.renderLogos) {\n    <span class=\"hljs-keyword\">const<\/span> value = el.dataset.renderLogos === <span class=\"hljs-string\">'true'<\/span>;\n    renderOptions.renderLogos = value;\n  }\n\n  <span class=\"hljs-keyword\">if<\/span> (el.dataset.renderTitle) {\n    <span class=\"hljs-keyword\">const<\/span> value = el.dataset.renderTitle === <span class=\"hljs-string\">'true'<\/span>;\n    renderOptions.renderTitle = value;\n  }\n\n  <span class=\"hljs-keyword\">return<\/span> renderOptions;\n};\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Cool, let&#8217;s take a peek at our tests now:<br \/>\n<figure class=\"Figure\"><\/p>\n<div class=\"u-pullSides1\">\n  <a href=\"https:\/\/codesandbox.io\/s\/solution-two-the-refactor-multiple-widgets-ysn3h?file=\/scripts\/utils\/get-render-opts-from-el.test.js\">\n<img src=\"https:\/\/cloudfour.com\/wp-content\/uploads\/2020\/10\/solution-2-tests-passing.png\" alt=\"'getRenderOptionsFromEl() should only return two attributes' test passes\">\n<\/a><\/div>\n<p><\/figure><\/p>\n<p>Alright! Both tests are now passing. \ud83c\udf89 We can <a href=\"https:\/\/ysn3h.csb.app\/\">confirm this via the browser<\/a> as well:<\/p>\n<div class=\"u-release\">\n<div class=\"u-containSpread\">\n  <a href=\"https:\/\/ysn3h.csb.app\/\"><br \/>\n<img src=\"https:\/\/cloudfour.com\/wp-content\/uploads\/2020\/10\/solution-2-browser-screenshot.png\" alt=\"A browser screenshot with the console log showing expected output for solution two\"><br \/>\n<\/a><\/div>\n<\/div>\n<h2>I fixed the broken code, but&#8230;<\/h2>\n<p>The second iteration of my solution is better than the first one. But I couldn&#8217;t help noticing it isn&#8217;t the most flexible. Consider the following: What if the requirements change, as they often do, where the design needs to support five, six, or an unknown amount of attributes with unknown names? It&#8217;s to nobody&#8217;s benefit for a developer to keep going back and adding one-off conditional checks for each new <code>data-render-*<\/code> attribute that might be added in the future.<\/p>\n<p>Ideally, the solution should minimize the chance that further maintenance or one-off conditionals need to be added down the line. I decided to look at one more solution iteration.<\/p>\n<h2>Embrace the Test-Driven Development spirit<\/h2>\n<p>Before working on a third iteration, I embrace the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Test-driven_development\">Test-Driven Development (TDD)<\/a> spirit and first add a failing test to help guide me:<\/p>\n<pre aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">test(<span class=\"hljs-string\">'getRenderOptionsFromEl() handles unknown attributes'<\/span>, () =&gt; {\n  <span class=\"hljs-comment\">\/\/ Create mock element<\/span>\n  <span class=\"hljs-keyword\">const<\/span> el = <span class=\"hljs-built_in\">document<\/span>.createElement(<span class=\"hljs-string\">'div'<\/span>);\n  <span class=\"hljs-comment\">\/\/ Add data attributes<\/span>\n  el.setAttribute(<span class=\"hljs-string\">'data-widget-id'<\/span>, <span class=\"hljs-string\">'321'<\/span>);\n  el.setAttribute(<span class=\"hljs-string\">'data-render-flags'<\/span>, <span class=\"hljs-string\">'false'<\/span>);\n  el.setAttribute(<span class=\"hljs-string\">'data-other'<\/span>, <span class=\"hljs-string\">'Some other value'<\/span>);\n  <span class=\"hljs-comment\">\/\/ Add a couple of new, unknown data-render attributes!<\/span>\n  el.setAttribute(<span class=\"hljs-string\">'data-render-the-future'<\/span>, <span class=\"hljs-string\">'true'<\/span>);\n  el.setAttribute(<span class=\"hljs-string\">'data-render-the-unknown'<\/span>, <span class=\"hljs-string\">'true'<\/span>);\n\n\n  <span class=\"hljs-keyword\">const<\/span> result = getRenderOptionsFromEl(el);\n  <span class=\"hljs-keyword\">const<\/span> expected = {\n    <span class=\"hljs-attr\">renderFlags<\/span>: <span class=\"hljs-literal\">false<\/span>,\n    <span class=\"hljs-attr\">renderTheFuture<\/span>: <span class=\"hljs-literal\">true<\/span>,\n    <span class=\"hljs-attr\">renderTheUnknown<\/span>: <span class=\"hljs-literal\">true<\/span>\n  };\n\n  expect(result).toEqual(expected);\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>And, confirming my hunch, the test fails:<br \/>\n<figure class=\"Figure\"><\/p>\n<div class=\"u-pullSides1\">\n  <a href=\"https:\/\/codesandbox.io\/s\/solution-two-multiple-widgets-unknown-data-render-attributes-l8yc0?from-embed=&amp;file=\/scripts\/utils\/get-render-opts-from-el.test.js\">\n<img src=\"https:\/\/cloudfour.com\/wp-content\/uploads\/2020\/10\/solution-2-tests-failing.png\" alt=\"'getRenderOptionsFromEl() handles unknown attributes' test fails\">\n<\/a><\/div>\n<p><\/figure><\/p>\n<p>Perfect! With the confidence-boosting failing test in my back pocket, I excitedly move forward toward a third iteration. \ud83d\ude42<\/p>\n<h2>Solution three: For the win!<\/h2>\n<p>While this might&#8217;ve been the most challenging solution to come up with, I enjoyed it the most when it came to problem-solving for it. After layered progress in the form of failures, the following solution eventually came to light:<\/p>\n<pre aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ scripts\/utils\/get-render-opts-from-el.js<\/span>\n\n<span class=\"hljs-comment\">\/**\n * <span class=\"hljs-doctag\">@typedef <span class=\"hljs-type\">{Object}<\/span> <span class=\"hljs-variable\">RenderOptions<\/span><\/span>\n * <span class=\"hljs-doctag\">@property <span class=\"hljs-type\">{boolean}<\/span> <\/span>&#091;renderCallToActionButton]\n * <span class=\"hljs-doctag\">@property <span class=\"hljs-type\">{boolean}<\/span> <\/span>&#091;renderFlags]\n * <span class=\"hljs-doctag\">@property <span class=\"hljs-type\">{boolean}<\/span> <\/span>&#091;renderLogos]\n * <span class=\"hljs-doctag\">@property <span class=\"hljs-type\">{boolean}<\/span> <\/span>&#091;renderTitle]\n *\/<\/span>\n\n<span class=\"hljs-comment\">\/**\n * Generates an object literal with render option properties\n *\n * <span class=\"hljs-doctag\">@param <span class=\"hljs-type\">{HTMLElement}<\/span> <span class=\"hljs-variable\">el<\/span><\/span>\n * <span class=\"hljs-doctag\">@returns <span class=\"hljs-type\">{RenderOptions}<\/span><\/span>\n *\/<\/span>\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> getRenderOptionsFromEl = <span class=\"hljs-function\"><span class=\"hljs-params\">el<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> renderOpts = {};\n  <span class=\"hljs-keyword\">for<\/span> (<span class=\"hljs-keyword\">const<\/span> attrKey <span class=\"hljs-keyword\">in<\/span> el.dataset) {\n    <span class=\"hljs-comment\">\/\/ We only care for attribute keys that<\/span>\n    <span class=\"hljs-comment\">\/\/ begin with 'render', per the requirements.<\/span>\n    <span class=\"hljs-keyword\">if<\/span> (attrKey.startsWith(<span class=\"hljs-string\">'render'<\/span>)) {\n      <span class=\"hljs-keyword\">const<\/span> attrValue = el.dataset&#091;attrKey];\n      <span class=\"hljs-comment\">\/\/ Add the attribute key and value, as a boolean,<\/span>\n      <span class=\"hljs-comment\">\/\/ to the output object.<\/span>\n      renderOpts&#091;attrKey] = attrValue === <span class=\"hljs-string\">'true'<\/span>;\n    }\n  }\n  <span class=\"hljs-keyword\">return<\/span> renderOpts;\n};\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h2>A final test run<\/h2>\n<p>Okay! Now let&#8217;s take a peek at the tests:<br \/>\n<figure class=\"Figure\"><\/p>\n<div class=\"u-pullSides1\">\n  <a href=\"https:\/\/codesandbox.io\/s\/solution-three-for-the-win-unknown-attributes-2liof?from-embed=&amp;file=\/scripts\/utils\/get-render-opts-from-el.test.js\">\n<img src=\"https:\/\/cloudfour.com\/wp-content\/uploads\/2020\/10\/solution-3-tests-passing.png\" alt=\"All tests are passing!\">\n<\/a><\/div>\n<p><\/figure><\/p>\n<p>Yay, they all pass! \ud83c\udf89 Allow me a moment to high-five myself. \ud83e\udd13<\/p>\n<p>We can <a href=\"https:\/\/2liof.csb.app\/\">confirm in the browser<\/a> as well:<\/p>\n<div class=\"u-release\">\n<div class=\"u-containSpread\">\n  <a href=\"https:\/\/2liof.csb.app\/\"><img src=\"https:\/\/cloudfour.com\/wp-content\/uploads\/2020\/10\/solution-3-browser-screenshot.png\" alt=\"A browser screenshot with the console log showing expected output for solution three\"><\/a><\/div>\n<\/div>\n<h2>Web development is a craft, enjoy the&nbsp;journey<\/h2>\n<p>Some key moments that stood out for me as I iterated toward a solution:<\/p>\n<ul>\n<li>Bugs provide an excellent opportunity to add tests. The tests will guide you during your logic refactor to fix the bug and future-proof your code from regressions.<\/li>\n<li>I found an opportunity to use the spirit of Test-Driven Development. I think it&#8217;s neat to sprinkle in moments to use this strategy without needing to subscribe to an all-or-nothing approach.<\/li>\n<\/ul>\n<p>In sharing my experience, I hope to show how our first attempts at a solution aren&#8217;t always correct. <em>And that that&#8217;s okay!<\/em> Just like writing an essay, it&#8217;s about getting the first draft out then iterating on it. As a developer, it doesn&#8217;t matter if you&#8217;re getting started in your career or have plenty of years of experience, don&#8217;t get discouraged by writing broken code. Instead, embrace it and get excited about the opportunity to fix the logic flaw by iterating toward a better solution. \ud83d\ude42<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Join me in exploring a recent experience where I started with flawed logic (without realizing it) and the steps I took to fix my bug. Let\u2019s experience some broken code together. \ud83c\udf89<\/p>\n","protected":false},"author":15,"featured_media":6058,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[256,240,205],"tags":[81],"class_list":["post-5898","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-development","category-javascript","category-process","tag-javascript"],"acf":[],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/cloudfour.com\/wp-content\/uploads\/2020\/10\/perfectly-broken-code-preview-2.png","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/cloudfour.com\/wp-json\/wp\/v2\/posts\/5898","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cloudfour.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cloudfour.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cloudfour.com\/wp-json\/wp\/v2\/users\/15"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudfour.com\/wp-json\/wp\/v2\/comments?post=5898"}],"version-history":[{"count":0,"href":"https:\/\/cloudfour.com\/wp-json\/wp\/v2\/posts\/5898\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudfour.com\/wp-json\/wp\/v2\/media\/6058"}],"wp:attachment":[{"href":"https:\/\/cloudfour.com\/wp-json\/wp\/v2\/media?parent=5898"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudfour.com\/wp-json\/wp\/v2\/categories?post=5898"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudfour.com\/wp-json\/wp\/v2\/tags?post=5898"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}