<?xml version= "1.0" encoding= "utf-8" standalone= "yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"
  xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Programming on Spinning Code</title>
    <link>https://spinningcode.org/tags/programming/</link>
    <description>Recent content in Programming on Spinning Code</description> <generator>Hugo -- 0.157.0</generator>
    <language>en-US</language> <lastBuildDate>Sat, 21 Feb 2026 21:19:59 +0000</lastBuildDate> <atom:link href= "https://spinningcode.org/tags/programming/feed.xml" rel= "self" type= "application/rss+xml" /> <item>
      <title>Estimates Tool Expansion</title>
      <link>https://spinningcode.org/2026/fib-estimates/</link>
      <pubDate>
        Sat, 21 Feb 2026 21:19:59 +0000
      </pubDate> <guid isPermaLink="false">https://spinningcode.org/2026/fib-estimates/</guid>  <description>Adding story points, t-shirt sizing, and other new features to my estimating tool.</description> <content:encoded><![CDATA[<p>Several years ago <a href="/2017/06/time-estimation-making-up-numbers-as-we-go-along/">I created a tool to help me estimate projects</a>.
It uses a <a href="https://en.wikipedia.org/wiki/Monte_Carlo_method">Monte Carlo</a> approach by taking your task list, estimated hours range, and your confidence in your own estimates.
From those inputs it generates a series of possible outcomes to give you a sense of how long the project might take.</p>
<p><a href="/estimates">Try my estimation tool.</a></p>
<h2 id="quick-history">Quick History</h2>
<p>At the time I created it I was struggling with the accuracy of the estimates I was doing.
I was either underestimating because of <a href="https://en.wikipedia.org/wiki/Optimism_bias">optimism bias</a>, or over estimating because I offset my bias too much.</p>
<p>The solution as startlingly effective.
I used <a href="https://www.joelonsoftware.com/2007/10/26/evidence-based-scheduling/">someone else&rsquo;s ideas</a> and built a <a href="/2017/06/time-estimation-making-up-numbers-as-we-go-along/">basic tool</a> and changed my behavior.
That first version was enough to vastly improve my estimating ability, and give me a tool to help other team members think about their own.
When I&rsquo;ve been able to check estimate vs actual all but once I&rsquo;ve been within on standard deviation of the mean (the &ldquo;likely range&rdquo; in that tool&rsquo;s results).</p>
<p>A few years later I needed to estimate costs in addition to hours.
So <a href="/2022/01/project-estimates-tool-2-0/">I created a second version</a> which added cost projections to the process.
That helped in a few scenarios, but honestly not as much as I&rsquo;d hoped.
Still, I learned stuff, therefore making it a useful exercise.
Mostly it got to me fix some old bugs, and add scatter plot graphs to deal with complex outputs.</p>
<p>Those versions were driven purely by hour range estimates.
They were extremely useful in helping me improve my estimates, if limited in estimating approach.</p>
<h2 id="time-to-add-new-approaches">Time To Add New Approaches</h2>
<p>Now I work on a team that estimates with <a href="https://www.atlassian.com/agile/project-management/fibonacci-story-points">Fibonacci story points</a> and <a href="https://www.easyagile.com/blog/agile-estimation-techniques">t-shirt sizing</a>.
I need my tool to support those approaches.</p>
<p>So over the last few weeks I&rsquo;ve done more updates and upgrades.
I now have a new version that will let you estimate using either of those techniques.
It automatically converts those estimates to day ranges and runs the same simulations as it did before.</p>
<h3 id="new-options">New Options</h3>
<p>My experience is that teams using Fibonacci estimates, story points, or t-shirt sizing are usually estimating in days.
Calendar time, or business days, are often what matters to product teams or in house teams.</p>
<p>Consultants track billable hours.
In house teams hit a deadline.</p>
<p>To give users control over the conversion between the raw inputs and the day ranges the tool offers two modes.
Which mode is best depends on what you&rsquo;re doing, how you team works, and how well you track productivity now.</p>
<p>The first is a simple points to days conversion that takes maps the Fibonacci number to a number of days.
It is labeled &ldquo;Calendar Days&rdquo; and lets users control the mapping.
This approach is better for long-term estimation covering months or more.</p>
<p>The second mode is for teams that are good at measuring their velocity for agile methodologies.
This mode let&rsquo;s users specify a number of points per sprint, and the length of a sprint.
It uses those factors to determine the number of points per day, and converts from there.
This approach is better for shorter term estimate cover a few sprints.</p>
<h3 id="other-updates">Other Updates</h3>
<p>While I was doing those updates I addressed a few other long standing issues.
Older versions lacked feedback that a simulation was running.
I also fixed several <a href="/2025/empathetic-accessibility/">accessibility issues</a>, improved test coverage, and added line item results summaries.
The ReadMe file is now far more complete with better descriptions of process and more instructions.</p>
<p>Importantly, I changed the behavior for under-run estimates.
Previously 50% of the time when the simulated value was outside the range provided it would pick a number below the specified range.
Now that happens only 25% of the time, the other 75% generate numbers above the range.
It also uses a calculation similar to the existing overestimate approach – the lower your confidence the wider a range it uses.
This greatly reduces the number of 0 hour/day tasks in the simulations.
It also changes the typical graph shape a bit to long-tail more on the high side.
I think that is likely more realistic for most projects.</p>
<h3 id="welcome-our-new-ai-overlords">Welcome Our New AI Overlords</h3>
<p>To accelerate the updates I used Copilot to help draft and review the code.
AI coding still feels like cheating, but it <em>is</em> far faster.
The code new is a bit sloppier in spots than I&rsquo;d like and required several rounds of revisions at times.
However, the AI added a lot of speed and reduced research level for new features.
That saved time meant I added polishes I&rsquo;ve been thinking about for years, but never took the time to do.</p>






<figure class="figure-figure-right">
  
  <a href="/images/posts/estimate-tool/ScatterPlotWithKdeLine.png" target="_blank" rel="noopener noreferrer">
    
    

    











<noscript>
  <img class="rcf-image" src="/images/posts/estimate-tool/ScatterPlotWithKdeLine.png" alt="A scatter plot graph, with a red trend line over the top outlining the graph&#39;s curve." loading="lazy" />
</noscript>

<img class="rcf-image lazyload show-if-js"
     data-srcset="/images/posts/estimate-tool/ScatterPlotWithKdeLine_hu_de90c12f91c4c902.png 680w, /images/posts/estimate-tool/ScatterPlotWithKdeLine_hu_d9c85115efc91e79.png 850w, /images/posts/estimate-tool/ScatterPlotWithKdeLine_hu_55002b8f0a53c692.png 1020w, /images/posts/estimate-tool/ScatterPlotWithKdeLine_hu_411e3066dfd8f994.png 1360w, /images/posts/estimate-tool/ScatterPlotWithKdeLine.png 1648w"
     data-src="/images/posts/estimate-tool/ScatterPlotWithKdeLine.png"
     src="data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAABhCAIAAACRRD67AAAU9ElEQVR4nNQdXW8jSbGrqntm7IQod7tCB7wchxAP8AL//zegE49IIKHTPu3Cfib2TH8UqqqZtuOMc8nerj2pXbiNM7Zrqrq&#43;P8bf3Ny4e8DM9R&#43;gcP&#43;aRQDz&#43;Bdg/PvcwN9/qeyBXOE9ES2RB6VwShyjK8UhQhOAvEM8N1pPg0MGMHOMcbPZxBiZGRHX63XXdYtjALNQf7spfa8MIMytW63gucnBDANSSpvNZhgGAPDet21bNdJygEvhOPAwOMEycMk89I5QRIHo3Ng9AQ4ZAAAhhPV63TRNKQUAEHGBx9&#43;Z8nEOQ&#43;MQ3CAHx/UD&#43;CBaaGkIH4d5BhBRzjnGmFLC5WlVOf4pupQdeUcEOYkWMqWUInh58dw4PhbmiVsP/hJdIGaXs9BaPQSHKH4QovzbOZEMZca5sXwszNgAO/tZgRXOhNsREBSTS1l0vfcFiRvnigdmFwfOGVISCVjauTkC817Qp0&#43;fYoxmhEMIZ8JtHpiLKB8ujgKTzw4KBkBGZihFeZAcN8&#43;D/LMqyE59DQWWJQGifwrnLAeciAEKu&#43;JccVAANAgAzsWV/Fy00LwRvri46LoupWShwJlwmwO1tC5nUTLknQMH7JTULCzxjqLLiUVB&#43;WehhWYY4L1HxFLKAr0g0T85cckQ1PyCc5qGYHbgXBHPgVwaID8bCfgZ4i7LCE/6h9kVwKLJiPEFFQN2wHrq2TTncjA/DvNGuO/7lJLlghZkhEdPP4vTiVTYpcJJaC2hGACIGKAYBtFRpqYWD/MMuLm5GYYBES0VcSbcDoHVRy45c2gYsag8jF4CSigGCEXCF4SUMCUIYflmYCYbioh26i0cW4oNUJUi9FYUHSA7dYXk32IAnEiAuEPOAWbx34B56eSfNcJN0yBiztm00IIi4ZxdKYDEJPrHokRCJISdskdxRlmT1btSwYJhhgEmAXbwU0pLYYDqn1GzI2V2MRUVBiExuGpxNRpAFNOc8/Izo/MFmZRSlYCleEHi&#43;mRxQ1Gi31wmrPacHVFRwg4kIpEACYmXbgbmc0GbzWa73TLzYlIRFp2ranGQ9J&#43;EQASexOzaLwuLVwoOSByh4p5DVm5GAqwmY2Ew6L2dA7EDnFSnq6PDgGJmQagfSKul&#43;stcxD8qzB7AyTVOfhA7LNb63DdwFGYYQETr9dp7n3M2p&#43;gciN0FiwCyRAAFqTjLPoBYXNMwek6KyAEXcAxYQKJ5FQJaMP3njDARdV0XQhiGYSlG2AxAyYVCUv/HAl43FYBNIDxjERXEmpPAkhKkBH7RSaEZCaiwCNIrmAEQLjhgOyUI8seNCgZBjAGIcS6qdhwDsMZpCzcD80Z4GAaryTDz&#43;Y2wEN4MgLiYREhIXiywnHOhv/HAAYtLiqb6hQFqOdgtOhybkYADL&#43;j8qQg9FJxzAWQkQPQk1Cetlu4fb7RXwGWGsTRvnMPlhmMzDDAz4L2XOHMJLVlaBBYXh4LF5RIt2v/Uv2E3hWH626L/ZSKx2DnDsmsDIwNqwIUi42RtKRaInd0LshAA7IRrzcterxSF0RwzGjOK/FisqSAnXHZtwBv1h2Ho&#43;x4R27b1CktJRUwGQEIS0T87dPbde81FaE5Oaa15OxCDIOFZWXIg4M3qfvr06e3bt977b7/99uLionaInr8gYwYgZQbMgMwgDqbq&#43;gOa3jknPGa1RGLYzMBCu7VGCdhsNu/evbNqsCkf84KsRHxOBCcDkNAnFoeHxL7ep/90uWkhtRAUApSs/Euw1EYVv98LhIjVDd1ut6WUEILVxc4F1qAhWEn85WhydR4mppUHtG0Ci5aIeam1AV8Lv&#43;bzmP9j1vj8qYidAUCHcoSV9EePv5EeweWxfqw/Fiazw4usDcx4QZYBbdv2/KmIKQUkbn8Qv&#43;Dn&#43;4SVP4ggUbCcejXPCy4Rj6fbmlBijKZw4C6cDTtNAZkJRSJPEoIhPiAAJgFACJ4kZkbvHVJJiVNapjM6SgAAWB3GSjHWEWQZ6bMZYdP&#43;WS0QokMQetLD9HduKhSDdqgULmIJYualmgG/X3wvpRj1Y4w1FXE2IzxlIBjREaH2a&#43;PjJBK0YU7rAlgQix4vXKQzesfA8pjFgiXMZbAagJJydpgcllExPvK9Y3UsFVeQ2ErEi9RCOy/IzG&#43;FEEIdEzsDXur/iEdcSkKXMntkVZePPRM2RZANfySX4zK10C4ZZzGXzURaRggAzugF1QDYuhCfeHTZuobUZUVtombMeYEhMdasg3WjkPpqfBdOj5YOIaWcUgFkPRBP1YjmjIoddi47zAxFPdons/Irw5gLsi5oaweqdtgCsTN4Qap/iuZii/cM4lB6tcCP1kCA4gtBKVAcMCEjmRngEGBJ8YC3WQwA6LputVrZeN5WwXzQM3hBNgSQkgPgKQC24/xI&#43;oMVyEZ3AhyTOKNJfKqlaSFvvYjX19cXCiEEAGjbFgByzmfpDR39H3FAiYlg8j4ff/5HHmhEVhiYJTDjDCUmiAmXUGWawBu5v/nmm1LKfiKoaZqaED0pRtYCnRI7yNoEh26Xgn4aqBZihiKmznOiHCPECE1YzkC9twJkPeb7d6lO4Kmbc7kwxygMQILgtQhvPRBP/qhavGTtFBJ5YvlkWlJeaJeKqC/V7LTFASf1gqz/R/RPycE7pKCbQqbq71NBS/QA7DixY5BzZvs92HvARTRszTfn9n1/e3ubcyaik3pB4wxM0voXsYOgiZ3P0T9jjczo7zKrZ0S&#43;pKHECCFwgNpXd0aYH1O1pJCpoNMJgWXfYtQGCPFbdor/c8k01g9QPwARQnBIJaYyRE3znT8mmJ&#43;QMS&#43;opiJOYwaEzzHlIebiOBAoA8wAPMn/OYDRHSLQ1l4PJfDQ8zCArjlwdGYRODqiZNUxm5c/BSJjLbTPKWX0RYIlObi/fBMWOPGFPGLWZolM3oFoOd/3IAGBh7P23RztDT1xBkKUzxDzMGjg6gt&#43;htdzBPZqZDrPig69SxmG3iEQrMTOnM8UzBthm1SNupDnFEbYMj99n1LOFAqJ&#43;TW98ZT4dx6gZuVqii4EcbbiwJstWJh3vp6JmeZc69O6vb1NKZ2iN9Qir75Pw5AYI1ApQOCC6g36EgbIhoh3jaSIkQLH7GOEzVZY0Lbn4sHMfICFZpaT&#43;OqpCFP92z5u&#43;lRcJi/eJ0t8qDWwL6McaloCwcU8da2DL1AgJne78cyVBycOPOeNsE2qfvVUxI76myGm7AP7YJVHr&#43;nPX6p99sAGCGy2O&#43;aSiig5RwFKKn30ufiUqW0xhC8jdI&#43;Go93RtVflq2BjsYVqnrzZZlH9PmJwmnkORG3QFMSX&#43;2qt1EMgzJ6ybphgcA59RizRpSH6lH1MvutC1yD5z0l9fBbMD&#43;kdBGJf8PvGsE6sbi5DL8c/5oSUKCT1BwNA8I/qfng6yBcEQtcIb4sIgXpfADkCp6HcbjlnKMV3rTXAnMAqzDOgrg61ZPWX&#43;SprM8&#43;lpCTupjidccglAmXyrObGy/HXe//M5M9DYArNk0i4qCPVRRJrEjE0CUB&#43;6CUOb1IMnaij0TJPOY0vi4/BPANqMo6IflFBhu3PtOYhpTTE1A9RVH6JDL0T6kMBj9AQtoGaQISP7D55Mqhb4Wz2tqAqIucyu8LA6DFgSVF7ojZtTE0b0HvbwThKw8iML4nbvA0IIaxWKxuSeaoXtGvQr9rGaoE5l5RTTGIDGSKGBFTUDfdEbUNdQ20gT5&#43;ZensMgJvSc&#43;qXBuJEqPN/wIiaAQQGLCWJdJTiKSLROI0m8bQ3mWCobaq/FNF5L8gGNB5ORexPPewTfTrs2Yq6jov2lxfHXAATUUKfHLIEp9jqfYVAXaDgyeM4ePT1wNSJpSGDR&#43;e8p5IV35hK1spBZh&#43;FI074UgqmHjSVB&#43;QdodNDI4zxHvYSZbshKWexx&#43;77HkgnzqciDojOY2YC6kTWOJc1TWftnfQs1NeSug1ZqhNOuuCT7JSV7FIS4feETRCfJ3hsvLl/p/P/zC/SYXvUFsgSqQyppFwKo0PggAyu6IQByC0xiA8b7ejJ2yZDDUaZSUeBzc3aeMgobuBgXnMdNcKDjqqWUki7Ey2OhPH309xoGRcmyf/Zj7ZqVEe0CoXxLm3BntKXHIRUHGQu7EXpeyP9ialvoIlqiQfKOP8qscKQchoHolAzUt5NayR1X05WiyE37ProbHhtNAz6cTo16yycsb0tulSWvDfjdoDD0VyQpSLUMQ8EEZS4MNpUXY3BhScGmPQ5QrZ5RO3mBCKwhOZU&#43;TAae4Q2ECtPxeP8aib3MTAO2qtoqyMmcpnVQ63zaMXUKqsc2/KJOjhVyqQPnA7I1pnypAt9tRGP0PlAXde0DdwLbo6qIPV/dOZQ3QLISTSlKR3bhqGrkZwnU01gHYBEblp6jJZLcPWEOBhTYwiT/jor8ffuVsc6TLcwgp&#43;GXqv6ZQdm3WpMVMhzKJP&#43;vdPL5qZmNy5FO5OyGwYEYC9K&#43;OBuj6YiACAOQ&#43;x7NwzZsWhw8ozjiVZyj3/Hqd1R&#43;42CaP04ON1DZcAdM7WM4UUYTWbde8Blp9f1eOv5UZGfrhCHAtSQqy6YHG5jhcnTKAm5QE6YEtpyzXurW46mIrSrr5R&#43;EIvig2taF4KrA9JQDT3gZF4Q6o24KfCBvY91C6H4AzDuIQWuJnNiw7QXBEbh4Kp4VEe5cXWps7zTyBh27NkVj7rCZbYf6aGCTFH9hb4LmqWyxcCTwR/NwX6MWD8c7t7S5KI6V4uw/HA19vGDvV/tSji2YwJGXQWTbExTyjz92riIOrnM5hE5sgN6H47mgsQRijGza5oAFpTv3UTVe2YmxsY1vu9p3VGP0&#43;e7IxkmnvQs11m8IwmhwyvhqD577JVc/QuxAqAFWZyvy/Go&#43;essBUw7Gx07mLtsCv/cfRGYlwDLRhTd/6UN4rulGDxdsEvY6Xqesa8dDsPmsfk3S1igHNL92g4O6c&#43;ucLHr2J7cQeR1M8ohzta5q5cWVmJ5&#43;YOIh4StV2b17cXLHJvvD67UM5HHjf2FibDtmq5pJDJ0ByERl2x/srWUex/GtV37BLIOG0OyFPEQAYOXmBPuNjnM2wDLRgAgOxYH9qAxgjkzpzhsN5tBawbe03q1ptVqzOLvU7&#43;UOPTb7TbGKPfmfdt2TdOIQrx7XGIqMQ2Dzic78VCDX68a6pDw7pVlGHI/9H3fW&#43;Ne27Xtet0ED3fZX7jEIdUr5XPatvHrJuDdK8WX7ofYD9thGOR2hKJrF8SVPrzxwjnF7Vav1Irheq03jjRz432/2WxsxiJ4WnWd1vjuIDkvAdYYYX2ik5DBTp2D2h3N2OVBAhdXPDctOfb7TbR6pU5Y5xyHbE0ujqlr2kDe&#43;wOMCXJERpd13QYDQ0DXNXJo99PCIh&#43;cByiRkzgsDshJNNc1/iBtJewpqYcM4pUXZCIobYD28EoWMc4wuIKcuSQHEmoROD8FtrsbZ23ZHIbU91kiZOaS0bHHPZ9jd&#43;PJ&#43;jxE8DnwXF75aBxw1DsHW0yCbdsAuJRaS9i1bWuSuHufouuJuq5FBJs2IKKmacK9lBuza0K4vJCDXCeW27b1ZLMZe9QCbJvAlxdt29iVIYRWk6gHSON45WWnD4IysW6axh&#43;qNfmhaxtwF6tVZwpQkfR3yGAERmzaxjm2byciRZLAzd54B7YyR41K0zT31//A7JP0fhbuz8/UZ848fOUD48f1wRETVR66cv/5Erg3QHPQRmZXViQfGD48uPLY7dRC4Ze6nc9kwNnB/LTtdmtDDDbnbEJjBe1zI/hYeGhp35KBmbfb7f/&#43;97/tdlvPb9d1L168qMuOngU8VwbUFcvWQGZ1C5ODc&#43;P1NHiuDLDBnqurqzrJg4j26JtndPyfPQMQ8fLysgalNl9&#43;9uzqk&#43;AZM8Bi2v2cxkPe81LhuTLA4DlS/ABmmnNPj8Qxx/z0mBicpzWxjsyf&#43;KEN1n6x7zueC5P9pbUnmwsaGWCF&#43;Pfv379&#43;/frm5ialdILvrvmGb7755sWLF&#43;v12nR6Sun9&#43;/dv3rz5&#43;PHj2FpxEjAzfnV19fLly6urq9NsDd7tjLu5ufn3v//9448/vnr1qu/7r/3FBoh4fX39pz/96a9//WvTNCEEZr69vf3Pf/7z97///aeffjoZJsaAruu&#43;//77v/3tbz/88MN6vT4dA&#43;y2f/rppx9//PFf//qXPc&#43;8XrSfS3nSp&#43;8/g6MK9a6Yo8mDb7/91jn3/fff//rXv7bVFJvN5tWrV//4xz/&#43;&#43;c9/9n0/i8lTkTkQo30Ns5&#43;tWq/X79&#43;//&#43;67737729&#43;uVqsn3eznwR0VZPtz3717x8y2uKOmveya/XkNu4G6T2JX95k62uuzQOsT&#43;SwFbR9oT643Xffu3TtL2dsnp5Rubm7eKliCYf&#43;N1eunu9PuVhswnA/YU5dgm35DxPqZZm/qqtTtdvv27VtTwqdRfXeMcL3Dtm1fvny5Xq8Ne1PEVlgwvGvj4jAMtuPSyGf3b3dl2iOEYPffKViNwUj85s2bDx8&#43;1LxmRWMfk6ZpXrx4cXFxUVfaWRrOPrmq6ZTSMAze&#43;67r6lmp9K1LaG0zv&#43;m91WpV3xtj/PDhw&#43;vXr0//EN&#43;j9QCbDrOdKXVwTItfvvoJJjeWEzfCWSBqjLFDZ6kCI5lR2VjYNM1jsmZ2qK3YUHmz3W5TSpZhNzSM4nalrTwyPC1ZZKalaZpKXONNfVdl5&#43;lTSXdWlhlYovH169cfP36sGtxobTS1EWLnnD1oY3&#43;Qz25jUDDtZHdlqsb4au&#43;15jsbgdqPpw7y5tvt9r///e/t7a0R2vCpyZ&#43;6ZqRiYjjbeffe27in1YKs2XsYBma&#43;ubmxN1ZB6RXsx1MuLNwxwMqbl5eXFxcXRsEDZ3S/D6C&#43;Ujdd2itGkfo8&#43;npxPVnVvBtfu667VNhP4hPRarUyTOy5rgdFi/uYlLFFtVSc72NSpdD0EtwduzCedV33q1/9quu6kz25YmQAIq7X69/97nd/&#43;ctfrq6uPtv52&#43;fNY64nouvr6z/&#43;8Y/X19cmGYi4Wq1&#43;85vf/PnPfzYGfJ46fiom9pbVavXDDz989913XdedmgEXFxd/&#43;MMfLi8vP378eMpAzKooL1&#43;&#43;rIduvV7//ve/N4/wZJhUDXl9fX1KBuxKkvUBeicOPu22q23Yn5E6JSYVH5uRPpkKOqwJnysFNlvUPgsmZ0vGnf67H4blYPJV4f8BAAD//4OHUh0TP21GAAAAAElFTkSuQmCC"
     data-sizes="auto"
     width="1648" alt="A scatter plot graph, with a red trend line over the top outlining the graph&#39;s curve."
     loading="lazy" />



  </a><figcaption>New scatter plot display with KDE line, and save buttons.</figcaption></figure>

<p>For example the tool now outlines scatter plots using a <a href="https://towardsdatascience.com/kernel-density-estimation-explained-step-by-step-7cc5b5bc4517/">kernel density estimate</a> curve.
I added save buttons for the images, and there are print styles that make it easy to print into a PDF.
Hopefully all those changes will make it easier to share the results.
I only bothered to make those changes because AI made it easy to add them.</p>
<p>That process was aided by having had a reasonably good structure to the code I wrote in earlier editions.
And I did check all the new work, but keeping tabs on 200 lines of new code from one prompt is a challenging exercise at times.
The code base needs some additional refactors still to deal with some of the bloat it picked up.
I don&rsquo;t care that Claude and Copilot assure me all cleanup already been handled, the 2000 line <code>index.js</code> tells me otherwise.</p>
<p>You can find the full code my <a href="https://github.com/acrosman/simple-project-estimates">estimation tool</a> on Github.
If you have suggestions, please open an issue there and we can discuss it.</p>
]]></content:encoded> </item> <item>
      <title>My Self Documenting Code Doesn&#39;t Need to be in Git: and Other Lies Salesforce Developers Tell Themselves</title>
      <link>https://spinningcode.org/2025/06/my-self-documenting-code-doesnt-need-to-be-in-git/</link>
      <pubDate>
        Sun, 01 Jun 2025 00:00:00 +0000
      </pubDate> <guid isPermaLink="false">https://spinningcode.org/2025/06/my-self-documenting-code-doesnt-need-to-be-in-git/</guid>  <description>Developers love to lie to themselves. Herein you&amp;#39;ll find some of the worst lies Salesforce developers use on themselves.</description> <content:encoded><![CDATA[<p>One of the things that consistently surprises me as a developer in the Salesforce eco system are the lies developers are comfortable telling themselves. Some lies (like that their code is self-documenting) are common developer myths. Others (like not needing git) are Salesforce community specific.</p>
<p>So, as a public service to the community, I provide here a list of common lies and a sentence or two about why you should stop telling them.</p>
<h2 id="my-code-is-self-documenting">My code is self-documenting</h2>
<p>Not once in my career have I heard a developer say this and found their code to be &ldquo;self-documenting&rdquo;. Never. Nope. (If we&rsquo;re friends: yeah, not even yours.)</p>
<p>Developers who <em>claim</em> to write self-documenting code seem to solve problems in the least-obvious way available. Or at least it feels that way to me. Maybe if they left comments explaining <em>why</em> they used their approach it would seem brilliant. I don&rsquo;t know; it&rsquo;s never come up.</p>
<p>Self-documenting code doesn&rsquo;t mean following a variable naming convention and having <em>no comments at all</em>. You still need doc comments. You still need good functional decomposition. You still need comments to say <strong><em>why</em></strong> your code does things. Sure, I can usually figure out <em>what</em> it does, but not why.</p>
<p>If you still want to argue with me make sure you read <a href="https://web.eecs.umich.edu/~weimerw/2018-481/readings/mythical-man-month.pdf">Chapter 15 of Mythical Man Month</a> before calling.</p>
<h3 id="see-also-nobody-else-needs-to-understand-this-code-im-the-only-one-working-on-it">See Also: Nobody else needs to understand this code; I’m the only one working on it</h3>
<p>Just because you are the only one working on it right now, doesn&rsquo;t mean that will be true 4 years from now. I recently had to update code that had been running happily without attention for 5 years. By the time I was working on it, everyone had forgotten it <em>existed</em> let alone how it worked.</p>
<p>And don&rsquo;t forget <strong><em>you</em></strong> might be that person who needs to remember these details 5 years from now. Guess who I called first when we realized that code needed updating? Yup, the guy who wrote it. No, he didn&rsquo;t remember why he had made important choices either.</p>
<p>The only time I am <em>not</em> disappointed in the person who committed code in my name 6 months ago is when he wrote me really good comments to explain what he was thinking. When he didn&rsquo;t do that: that guy was an idiot.</p>
<p>Future you, your colleagues, friends, clients, strangers, they all will thank you for explaining yourself. Doesn&rsquo;t that feel better than having them all hate you?</p>
<h2 id="i-dont-need-to-use-git">I don’t need to use Git</h2>
<p><strong>Yes.</strong>
<strong>You.</strong>
<strong>Do.</strong></p>
<p>It’s 2025. Bell Labs <a href="https://en.wikipedia.org/wiki/Source_Code_Control_System">invented source control in 1972</a>. “I don’t know how to use it” isn’t a valid excuse. If you’ve been putting off learning to use these tools, it’s time you admit they aren’t going away.</p>
<p>Just because the code exists in a Salesforce sandbox org does <em>not</em> mean you don’t benefit from tracking its history, knowing who (including you) changed what when (and if <a href="https://xkcd.com/1296/">they wrote good comments</a> why!). The first time it saves you because another developer overwrote your code, it’ll have saved you all the time setup required and more.</p>
<p>I can setup a new repository, with Salesforce configuration, in 90 seconds. It takes 10 seconds to do a commit. Sure, you can have merge problems sometimes – that usually happens when two people make conflicting changes. When do you need source tracking the most? When two people make conflicting changes.</p>
<p>When you see two sandboxes with two different versions of code, how do you know which is right? On my projects it&rsquo;s the one that matches Git, cause Git is always canonical.</p>
<p>There really is no <em>good</em> argument against putting every line of code into a repo. Just because you <em>have</em> an argument does not mean you have a <em>good</em> argument. It&rsquo;ll take you less time to start using Git than it will take to argue with me.</p>
<h3 id="see-also-ill-just-comment-out-this-section-of-code-for-now-and-delete-it-later">See also: I’ll just comment out this section of code for now, and delete it later</h3>
<p>No you won&rsquo;t. Nope, stop kidding yourself. I&rsquo;ve seen your 200 line blocks of commented out &ldquo;stuff&rdquo; (without notes about why it was there in the first place, or why it&rsquo;s commented out). You want to keep it for 3 or 4 minutes while you test a change – okay sure. But this is part of what source control does for you. Deleted something you want back? Okay, go get it out of your source control system. Delete your block of commented out code before your next commit.</p>
<h2 id="i-will-fix-this-hard-coded-id-later">I will fix this hard-coded ID later</h2>
<p>If it&rsquo;s easy to fix, just do it now.</p>
<p>If it&rsquo;s going to be a bunch of work later, you know as well as I do that you&rsquo;ll find excuses not to bother at that point either.</p>
<p>Either write the query to get the Id you need now, or move it to custom metadata/setting/environment variable/whatever now. It&rsquo;ll take probably take 5 minutes to do it right now, and 20 minutes to <em>find</em> the stupid thing later (and 10 minutes to fix it cause you&rsquo;ll be tired from the 20 minute hunt).</p>
<h2 id="my-invocable-action-doesnt-need-to-be-bulkified">My invocable action doesn’t need to be bulkified</h2>
<p>Sure, damn near every example invocable action only processes the first record in the list. Yes, that encourages us to think it&rsquo;s okay to write code that way. And yes, bulkifying code is hard sometimes.</p>
<p>You might <em>think</em> that your record triggered flow will only need to deal with one user-entered update at a time. And then someone loads a 100,000 records and your action breaks the whole process.</p>
<p>And sure, you always <em>feel</em> like your action is only getting used in that one Screen Flow. But 9 months from now when someone realizes they can get the inputs other ways and call your action with the screens and puts it in a record triggered flow your code will still work.</p>
<p>There <em>exist</em> settings where the screen flow thing is true. They are rare, but I&rsquo;ve seen them. Use this measure to determine if it applies to your project: if you cannot write a rigorous proof which is good enough for your high school geometry/trig teacher (college CS or Math Professors will also count here) to accept showing this code will only ever be run in a one-record context – you better plan for 200 records.</p>
<h2 id="i-will-convert-this-test-setup-to-use-a-data-factory-when-i-have-more-tests">I will convert this test setup to use a data factory when I have more tests</h2>
<p>Why? Why would you think you will take the time to do this conversion later? When you arrive at “later” you will just need one more object added to your setup. Or maybe 2. And you can copy-paste it into the next test setup method. Then may add 2 more objects. And then <a href="https://www.neverjust.net/">you’ll just</a>…</p>
<p>If you don’t lay down good patterns up front, you won’t convert to them later. Starting a <a href="https://trailhead.salesforce.com/content/learn/modules/apex_testing/apex_testing_data">test factory class</a> doesn’t take a lot of time. Just do it now, and thank yourself later when you realize you’ve slowly built a factory that’s supporting 5 different test suites and hundreds of tests.</p>
<h2 id="ill-move-my-business-logic-out-of-the-trigger-once-its-working">I’ll move my business logic out of the trigger once it’s working</h2>
<p>Once your code is working you have almost no incentive to clean it up. Chances are you&rsquo;ll just deploy your code and wash your hands of it. It wont be until you&rsquo;re neck deep in a conflicting sets of requirements that you&rsquo;ll decide it&rsquo;s time to refactor this mess – and then you&rsquo;ll wonder what each conditional was for in the first place (see self-documenting code above).</p>
<p>Even during a recent technical interview, I took the 15 seconds needed to create the trigger and have it call a service class. I didn&rsquo;t plan to keep that code for more than 15 minutes and I still bothered. I really think it took 15 seconds. Bing, bang, done. Sorting it out later is so much harder then just doing it on the first pass.</p>
<h2 id="as-longs-as-i-hit-the-test-coverage-requirement-my-tests-are-good-enough">As longs as I hit the test coverage requirement, my tests are good enough.</h2>
<p>Automated test coverage is not about hitting a certain number of lines run – that&rsquo;s the minimum threshold. Good automated tests ensure you code works <em>correctly</em>. Good test coverage now means that when you revise the code later you know if your revisions broke something.</p>
<ul>
<li>Do not play games with getting the coverage percentage up.</li>
<li>Do not have tests with no assert statements.</li>
<li>Do not decide that 75% is just fine because to get to 90% you&rsquo;d have to write three more tests – write the three tests!</li>
<li>Do not let test coverage meaningfully changes when someone auto-formats your code.</li>
</ul>
<p>Write tests that ensure your code meets specifications, not tests that make the deployment process happy.</p>
<h2 id="i-dont-need-automated-code-standards-enforcement">I don’t need automated code standards enforcement</h2>
<p><a href="https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/overview">Salesforce Code Analyzer</a> is a free tool you can install right now and will help ensure you don’t do stupid stuff in your code. It will also make sure you team follows the same habits. These tools have been around for Salesforce developers for years, but for some reason we don’t all consistently turn them on.</p>
<p>This is standard fare for professional developers. What makes you special that you don&rsquo;t need to follow standards? Why would you think you can enforce standards on a team without automation?</p>
<p>Every time I have to do a commit to reformat code to be consistent with all the other code on a project, a [insert sad thing here] happens. This pretty much only comes up with people who don&rsquo;t have auto-formatting of their own. Their code had blends of tabs and spaces, different line endings within the file, off-by-one spacing error (dear God I hope no one uses 3 on purpose), and all kinds of things that cause other editors to sputter and complain.</p>
<p>If you don&rsquo;t like Salesforce&rsquo;s default analyzer rules, you can setup your own. It&rsquo;s just enforcing ESLint and PMD patterns. I have extremely little interest in debating code standards – <em>just have some!</em></p>
<h2 id="special-thanks-for-shared-salesforce-developer-lies">Special Thanks For Shared Salesforce Developer Lies</h2>
<p>The following people sent me lies to include in this list, and I appreciate their shared pain. My rants about each are mine alone.</p>
<ul>
<li><a href="https://www.linkedin.com/in/peter-churchill-bfc/">Peter Churchill</a></li>
<li><a href="https://www.linkedin.com/in/michelleelizabethhansen/">Michelle Hansen</a></li>
<li><a href="https://www.linkedin.com/in/raul-iii-roque/">Raul Roque</a></li>
</ul>
]]></content:encoded> </item> <item>
      <title>SC DUG April 2021 - Getting Started with Electron</title>
      <link>https://spinningcode.org/2021/04/sc-dug-april-2021-getting-started-with-electron/</link>
      <pubDate>
        Wed, 14 Apr 2021 03:48:11 +0000
      </pubDate> <guid
        isPermaLink="false">https://spinningcode.org/?p=1643</guid>  <description>This month I gave a talk at South Carolina Drupal User Group on Getting Started with Electron.</description> <content:encoded><![CDATA[<p>This month I gave a talk at South Carolina Drupal User Group on Getting Started with Electron. Electron allows you to use your web developer skills to create desktop applications. I based this talk on some of my recent side projects and the <a href="/2020/12/salesforce-electron-starter/">Electron Project Starter</a> I posted the end of last year.</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/4BSLlEvdGoM?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>If you would like to join us please <a href="https://www.meetup.com/SC-Drupal-Users-Group/">check out our up coming events</a> on MeetUp for meeting times, locations, and remote connection information.</p>
<p>We frequently use these presentations to practice new presentations, try out heavily revised versions, and test out new ideas with a friendly audience. So if some of the content of these videos seems a bit rough please understand we are all learning all the time and we are open to constructive feedback. If you want to see a polished version checkout our group members’ talks at camps and cons.</p>
<p>If you are interested in giving a practice talk, leave me a comment here, <a href="https://www.drupal.org/u/acrosman/">contact me through Drupal.org</a>, or find me on Drupal Slack. We’re excited to hear new voices and ideas. We want to support the community, and that means you.</p>
]]></content:encoded> </item> <item>
      <title>Bypass Pantheon Timeouts for Drupal 8</title>
      <link>https://spinningcode.org/2019/07/bypass-pantheon-timeouts-for-drupal-8/</link>
      <pubDate>
        Wed, 31 Jul 2019 11:30:09 +0000
      </pubDate> <guid
        isPermaLink="false">https://spinningcode.org/?p=1145</guid>  <description>Using Drupal batches and old school PHP file handles to bypass Pantheon Timeouts. Import all the data you need.</description> <content:encoded><![CDATA[<p>Pantheon is an excellent hosting service for both Drupal and Wordpress sites. But to make their platform work and scale well they have a number of limits built into the platform. These include process time limits and memory limits. While they are large enough for the majority of projects, large projects can have trouble.</p>
<p>For data loading their official answer is typically to copy the database to another server, run your job there, and copy the database back onto their server. That’s fine if you can afford to freeze updates to your production site. And have the time to setup a process to mirror changes into your temporary copy. And can afford some additional project overhead. But sometimes those things are not an option. Or the data load takes too long, or happens too often, for that to be practical on a regular basis.</p>
<p>I recently needed to do a very large import of records into Drupal on a Pantheon hosted site. The minimize user impact I started to play around with solutions that would allow me to ignore those time limits. We were looking at dong about 50 million data writes for the project. When I first estimated the process the running time was over a week.</p>
<h2 id="the-outline">The Outline</h2>
<p>Since Drupal’s batch system was created to solve this exact problem it seemed like a good place to start. For this solution you need a file you can load and parse in segments, like a CSV file, which you can read one line at a time. It does not have to represent the final state of your data. While you can actually load the data raw, you can also load each record into a table or a queue to process later.</p>
<p>One quick note about the code samples, I wrote these based on the service-based approach outlined in my post about <a href="/2019/01/drupal-8-batch-services/">batch services</a> and the <a href="https://www.drupal.org/sandbox/acrosman/3025562">batch service module</a> I discussed there. It could be adapted to a traditional batch job, but I like the clarity the wrapper provides for this discussion.</p>
<p>The general concept here is that we upload the file and then progressively process it from within a batch job. My code samples below provide two classes to achieve this. The first is a form that provides a managed file field which create a file entity that can be reliably passed to the batch processor. From there the batch service uses a bit of basic PHP file handling to copy data into the database. If you need to do more than load the data into the database directly (say create complex entities or other tasks) you can set up a second phase to run through the values to do that heavier lifting.</p>
<h2 id="load-your-file">Load your file</h2>
<p>To get us started the form includes this managed file:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span>   $form[<span style="color:#e6db74">&#39;file&#39;</span>] <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>     <span style="color:#e6db74">&#39;#type&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#39;managed_file&#39;</span>,
</span></span><span style="display:flex;"><span>     <span style="color:#e6db74">&#39;#name&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#39;data_file&#39;</span>,
</span></span><span style="display:flex;"><span>     <span style="color:#e6db74">&#39;#title&#39;</span> <span style="color:#f92672">=&gt;</span> $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">t</span>(<span style="color:#e6db74">&#39;Data file&#39;</span>),
</span></span><span style="display:flex;"><span>     <span style="color:#e6db74">&#39;#description&#39;</span> <span style="color:#f92672">=&gt;</span> $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">t</span>(<span style="color:#e6db74">&#39;CSV format for this example.&#39;</span>),
</span></span><span style="display:flex;"><span>     <span style="color:#e6db74">&#39;#upload_location&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#39;private://example_pantheon_loader_data/&#39;</span>,
</span></span><span style="display:flex;"><span>     <span style="color:#e6db74">&#39;#upload_validators&#39;</span> <span style="color:#f92672">=&gt;</span> [
</span></span><span style="display:flex;"><span>       <span style="color:#e6db74">&#39;file_validate_extensions&#39;</span> <span style="color:#f92672">=&gt;</span> [<span style="color:#e6db74">&#39;csv&#39;</span>],
</span></span><span style="display:flex;"><span>     ],
</span></span><span style="display:flex;"><span>   ];
</span></span></code></pre></div><p>The managed file form element automagically gives you a file entity. The value in the form state is the id of that entity. This file will be temporary and have no references once the process is complete and so depending on your site setup the file will eventually be purged. Which adds up to mean we can pass all the values straight through to our batch processor:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span>$batch <span style="color:#f92672">=</span> $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">dataLoaderBatchService</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">generateBatchJob</span>($form_state<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">getValues</span>());
</span></span></code></pre></div><p>If the data file is small, a few thousand rows at most, you can load it right away. But that runs into both time and memory concerns and the whole point of this is to avoid those. With my approach we can ignore those and we’re only limited by Pantheon’s upload file size. If the file size is too large for that you can upload the file via sftp and read from the file system, so you have options.</p>
<p>As we setup the file for processing in the batch job, we really need the file path not the ID. The main reason to use the managed file is they can reliably get the file path on a Pantheon server without us needing to know where they have things stashed. Since we’re about to use generic PHP functions for file processing we need to know that path reliably:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span>$fid <span style="color:#f92672">=</span> <span style="color:#a6e22e">array_pop</span>($data[<span style="color:#e6db74">&#39;file&#39;</span>]);
</span></span><span style="display:flex;"><span>$fileEntity <span style="color:#f92672">=</span> <span style="color:#a6e22e">File</span><span style="color:#f92672">::</span><span style="color:#a6e22e">load</span>($fid);
</span></span><span style="display:flex;"><span>$ops <span style="color:#f92672">=</span> [];
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> (<span style="color:#66d9ef">empty</span>($fileEntity)) {
</span></span><span style="display:flex;"><span>  $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">logger</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">error</span>(<span style="color:#e6db74">&#39;Unable to load file data for processing.&#39;</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> [];
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>$filePath <span style="color:#f92672">=</span> $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">fileSystem</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">realpath</span>($fileEntity<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">getFileUri</span>());
</span></span><span style="display:flex;"><span>$ops <span style="color:#f92672">=</span> [<span style="color:#e6db74">&#39;processData&#39;</span> <span style="color:#f92672">=&gt;</span> [$filePath]];
</span></span></code></pre></div><h2 id="create-your-batch">Create your batch</h2>
<p>Now we have a file know where it is. Since it’s a csv we can load a few rows at time, process them, and then loop back.</p>
<p>Our batch processing function needs to track two things in addition to the file: the header values and the current file position. So in the first pass we initialize the position to zero and then load the first row as the header. For every pass after that we need to find point we left off. For this we use generic PHP files for loading and seeking the current location:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#75715e">// Old-school file handling.
</span></span></span><span style="display:flex;"><span>$path <span style="color:#f92672">=</span> <span style="color:#a6e22e">array_pop</span>($data);
</span></span><span style="display:flex;"><span>$file <span style="color:#f92672">=</span> <span style="color:#a6e22e">fopen</span>($path, <span style="color:#e6db74">&#34;r&#34;</span>);
</span></span><span style="display:flex;"><span><span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">fseek</span>($file, $filePos);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Each pass we process 100 lines, if you have to do something complex
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">// you might want to reduce the run.
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> ($i <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>; $i <span style="color:#f92672">&lt;</span> <span style="color:#ae81ff">100</span>; $i<span style="color:#f92672">++</span>) {
</span></span><span style="display:flex;"><span>  $row <span style="color:#f92672">=</span> <span style="color:#a6e22e">fgetcsv</span>($file);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#66d9ef">empty</span>($row)) {
</span></span><span style="display:flex;"><span>    $data <span style="color:#f92672">=</span> <span style="color:#a6e22e">array_combine</span>($header, $row);
</span></span><span style="display:flex;"><span>    $member[<span style="color:#e6db74">&#39;timestamp&#39;</span>] <span style="color:#f92672">=</span> <span style="color:#a6e22e">time</span>();
</span></span><span style="display:flex;"><span>    $rowData <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>             <span style="color:#e6db74">&#39;col_one&#39;</span> <span style="color:#f92672">=&gt;</span> $data[<span style="color:#e6db74">&#39;field_name&#39;</span>],
</span></span><span style="display:flex;"><span>             <span style="color:#e6db74">&#39;data&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#a6e22e">serialize</span>($data),
</span></span><span style="display:flex;"><span>             <span style="color:#e6db74">&#39;timestamp&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#a6e22e">time</span>(),
</span></span><span style="display:flex;"><span>    ];
</span></span><span style="display:flex;"><span>    $row_id <span style="color:#f92672">=</span> $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">database</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">insert</span>(<span style="color:#e6db74">&#39;example_pantheon_loader_tracker&#39;</span>)
</span></span><span style="display:flex;"><span>             <span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">fields</span>($rowData)
</span></span><span style="display:flex;"><span>             <span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">execute</span>();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// If you&#39;re setting up for a queue you include something like this.
</span></span></span><span style="display:flex;"><span>    <span style="color:#75715e">// $queue = $this-&gt;queueFactory-&gt;get(‘example_pantheon_loader_remap’);
</span></span></span><span style="display:flex;"><span>    <span style="color:#75715e">// $queue-&gt;createItem($row_id);
</span></span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>   <span style="color:#66d9ef">break</span>;
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>$filePos <span style="color:#f92672">=</span> (<span style="color:#a6e22e">float</span>) <span style="color:#a6e22e">ftell</span>($file);
</span></span><span style="display:flex;"><span>$context[<span style="color:#e6db74">&#39;finished&#39;</span>] <span style="color:#f92672">=</span> $filePos <span style="color:#f92672">/</span> <span style="color:#a6e22e">filesize</span>($path);
</span></span></code></pre></div><p>The example code just dumps this all into a database table. This can be useful as a raw data loader. If you need to add a large data set to an existing site that’s used for reference data or something similar.  It can also be used as the base to create more complex objects. The example code includes comments about generating a queue worker to run on cron or as another batch job. the <a href="https://www.drupal.org/project/queue_ui">Queue UI module</a> provides a simple interface to run those on a batch job.</p>
<h2 id="final-considerations">Final Considerations</h2>
<p>I’ve run this process for several hours at a stretch.  Pantheon does have issues with systems errors if left to run a batch job for extreme runs. I ran into problems on some runs after 6-8 hours of run time. So a prep into the database followed by running on queue restart has been more reliable.</p>
<script
  src="https://gist.github.com/acrosman/12555ca2f365592908c6020895c7691a.js"></script>
]]></content:encoded> </item> <item>
      <title>Helping the Cable Company Help me</title>
      <link>https://spinningcode.org/2017/07/helping-the-cable-company-help-me/</link>
      <pubDate>
        Wed, 05 Jul 2017 22:04:05 +0000
      </pubDate> <guid
        isPermaLink="false">https://spinningcode.org/?p=333</guid>  <description>After several hours on hold, 5 or 6 visits (I&amp;#39;ve lost count), and them testing everything in sight I finally started writing software to see if I can help find a problem on my internet connection.</description> <content:encoded><![CDATA[<p>Since the beginning of June I&rsquo;ve been having intermittent issues with my Atlantic Broadband internet service.  When it works, it&rsquo;s great ( <a href="/2016/09/bad-data-systems-do-not-justify-sexist-your-behavior/">except they still won&rsquo;t let my wife and I have different last names</a>), but when it&rsquo;s not it&rsquo;s really annoying.</p>
<p>After several hours on hold, 5 or 6 visits (I&rsquo;ve lost count), and them testing everything in sight, I finally started writing software to see if I can help find the problem.</p>
<p>At strange intervals there is a run of noise on the line that kills my service.  The noise eventually goes away and my service resumes. But figuring out those intervals has been a challenge since it turns out I don&rsquo;t actually sit around 24 hours a day watching for service interruptions.</p>
<p>So after the last tech was unable to find a problem, and implied that it was my cable modem, I wrote a <a href="https://github.com/acrosman/connected">simple python script</a> that pings Google every 5 seconds and records the response time (or failure). I setup an old Linux laptop to sit around and do nothing but ping Google 24 hours a day and record the results of each request.</p>
<p>I learned that my service interruptions tend to start and stop on the hour or half hour.  For example, the first night it started to be bad at noon and improved at 2:30 the next morning.  That pattern of human friendly times and data was enough to convince them to try again (although they aren&rsquo;t willing to consider it&rsquo;s something scheduled since they aren&rsquo;t aware of any scheduled processes on their network).</p>
<p>So at the moment I have a loaner modem – which has ruled out my device as the problem – and they are closing on having replaced every piece of hardware between me and the nearest node.  To their credit, it has gotten much less bad.  Most outages now last five minutes (more or less exactly), and I have only have a few a day (instead of a few an hour).</p>
<p>Anyone have guesses about what processes run on cable networks that last five minutes and reattempt for a few hours at a stretch before giving up for roughly 13 or 14 hours?</p>
<p>My takeaway so far has been that if your internet provider is unable to figure out what&rsquo;s wrong, offer them more data. Even if they don&rsquo;t know how to use it, it them to take the problem seriously.</p>
]]></content:encoded> </item> <item>
      <title>Drupal 8 Custom Plugins are addictive</title>
      <link>https://spinningcode.org/2017/01/drupal-8-plugins-are-addictive/</link>
      <pubDate>
        Sun, 08 Jan 2017 19:24:32 +0000
      </pubDate> <guid
        isPermaLink="false">http://spinningcode.org/?p=186</guid>  <description>Why and how to build your own Drupal 8 custom plugins.</description> <content:encoded><![CDATA[<p><em>In December I gave a talk at the <a href="https://www.meetup.com/SC-Drupal-Users-Group/">SCDUG meeting</a> on Drupal 8 plugins.  This is based on that talk with a few improvements and additions from things I&rsquo;ve learned since.</em></p>
<p>Drupal 8 provides many great improvements, but one I&rsquo;ve been finding to be the most exciting is the easy ability to create my own plugins. They are actually a bit addictive, and it&rsquo;s easy to start seeing projects as an excuse to create a new plugin manager. At first it can take a little bit of effort to get your head around the process, but I got great details from <a href="https://drupalize.me/blog/201409/unravelling-drupal-8-plugin-system">Unraveling the Drupal 8 Plugin System from Drupalize.me</a>. But the problem with those, and all the examples I could find, was they were either too deep (the drupalize.me article should be considered required reading, but the example is too abstract for most people) or too shallow ( <a href="https://www.drupal.org/docs/8/api/plugin-api/plugin-api-overview">the drupal.org Plugin API examples</a> currently lack context about why I would want to do this in practice).</p>
<p>So my goal is to straddle that gap.</p>
<h1 id="whats-a-plugin">What&rsquo;s a plugin?</h1>
<p>Plugins are a design pattern that let you extend a module or service. Drupal 8 has at least four plugin discovery systems:</p>
<ul>
<li>Annotated ← I’m mostly talking about this one, its used for blocks, views, rules, Queue API, and more.</li>
<li>YAML ← These are used for menus, routes, and services.</li>
<li>Hook ← D7 carry over hook system, still used in many places.</li>
<li>Discovery Decorators ← Wrap around other plugin systems to replace/improve on the classic info alter hooks from previous Drupal versions.</li>
<li>Static ← These are used for test code, and I&rsquo;m ignoring here.</li>
</ul>
<h1 id="basic-commonly-used-plugins">Basic Commonly Used Plugins</h1>
<p>As a Drupal 8 developer you probably use several plugins when building a site, particularly if there is any custom functionality required. When creating controllers you&rsquo;ll need a route, and likely a menu item, which means you&rsquo;ll use YAML files to define those aspects.  For <a href="https://www.drupal.org/docs/8/creating-custom-modules/create-a-custom-block">blocks</a>, <a href="http://www.vdmi.nl/blog/creating-drupal-cron-queue-worker-drupal-8">the queue api</a>, or <a href="http://uname.pingveno.net/blog/index.php/post/2016/07/27/Drupal-8-%3A-create-a-custom-Rule-action">Rules</a> you&rsquo;ll create an annotated plugin.  Theme functions are still commonly defined with <a href="https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Render!theme.api.php/function/hook_theme/8.2.x">hook_theme()</a>.</p>
<h2 id="why-do-i-create-new-plugin">Why do I create new plugin?</h2>
<p>Between core, and major contrib modules (like Rules and Search API), plugins options abound, but it&rsquo;s not always obvious why you might want to create one for a more focused project. For large projects that may have a large number of related functions, poorly defined use cases, or use cases that are expected to expand over time Plugins provide three major advantages over other approaches:</p>
<ul>
<li>Plugins make it easy to easily keep classes small and focused.</li>
<li>Plugins make it easy to extend a service.</li>
<li>Plugins allow one module to easily extend the function of another.</li>
</ul>
<h3 id="actual-examples">Actual Examples</h3>
<p>Cyberwoven has a client who needed an internal training portal. They want to provide some basic gamification, particularly badges, and they wanted a more detailed tracking of user behavior than the core statistics module provides. The badges present a function that has many related, but different, use cases and is likely to grow over time. So I created a pluggable service that allows us to easily define a set of conditions for creating a new badge. Each plugin&rsquo;s annotation lists the events it cares about, and when one of those events is fired the service runs the plugin&rsquo;s test function. If the badge has been earned the plugin returns the information needed to generate that entity. All the event listening, entity generation, and error checking happens in the service, so each plugin is only focused on the details of the one badge. The event tracker is similar, with each plugin defining a filter for the major system events to determining what details should be recorded, and the service handling the logging of those details and reporting. As the client needs have evolved we&rsquo;ve been able to add and modify the plugins to meet their needs.</p>
<p>The second place I&rsquo;ve created a custom plugin system for was an internal project at Cyberwoven (it was actually the project I used to learn how to create custom plugins). Our testing server has always had a service to perform some basic operations on the server, like pulling repos via webhooks and similar tools to help developers. Setting up our development environments for D8 meant it was time for a new test server, and therefore new tools to manage it. From the start I wanted to create a tool that helped both developers and account managers. So I created a D8 site to manage the sites that provides various tools to perform task operations we all need (not only code handling, but also checking if security releases apply to any sites). All the various operations we want exposed through that site are created as plugins to our custom manager. Each task plugin is focused on just that one task. All the other various needed to track the sites, route requests to run a task, and render their responses are all handled in other places.  It&rsquo;s also now making it easy to add features that wrap around the plugins since the plugins all have a consistent interface (like adding new displays and running some tasks through cron).</p>
<h1 id="introducing-site-manager">Introducing Site Manager</h1>






<figure class="figure-center">
  
  <a href="/wp-content/uploads/2017/01/Screen-Shot-2017-01-08-at-11.59.29-AM.png" target="_blank" rel="noopener noreferrer">
    
    

    











<noscript>
  <img class="rcf-image" src="/wp-content/uploads/2017/01/Screen-Shot-2017-01-08-at-11.59.29-AM.png" alt="List of example sites." loading="lazy" />
</noscript>

<img class="rcf-image lazyload show-if-js"
     data-srcset="/wp-content/uploads/2017/01/Screen-Shot-2017-01-08-at-11.59.29-AM_hu_414d0960c0180b66.png 680w, /wp-content/uploads/2017/01/Screen-Shot-2017-01-08-at-11.59.29-AM_hu_1e2b76bc8aa0b099.png 850w, /wp-content/uploads/2017/01/Screen-Shot-2017-01-08-at-11.59.29-AM_hu_eed366167d11b87c.png 1020w, /wp-content/uploads/2017/01/Screen-Shot-2017-01-08-at-11.59.29-AM_hu_7174d788ea914f8f.png 1360w, /wp-content/uploads/2017/01/Screen-Shot-2017-01-08-at-11.59.29-AM_hu_8712bd0f33756193.png 2040w, /wp-content/uploads/2017/01/Screen-Shot-2017-01-08-at-11.59.29-AM.png 2526w"
     data-src="/wp-content/uploads/2017/01/Screen-Shot-2017-01-08-at-11.59.29-AM.png"
     src="data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAAA3CAIAAABLk9VRAAAN40lEQVR4nOxc63bcto8HQJCSnLjdfGwfvU&#43;4/w/ds6eOxyOJJPYQoDicayZJc/Ma7mnGMxqJxPWHC81//fXXyzynGINzD0wjZM5JnFv9mJwXRHijb0kMAMv&#43;Zf/0j6A8BpowM2Ka3oMfkB0ggcpARAAAN3lc/PWcrr3/Ro14ZJcx&#43;/3TMD&#43;zgxVgHScMEzEPw&#43;i8L1yWwkoBISJEFJGcMwC0X43sjnZ9lpxSyjm/yeA28cQuYF5kQVkh03PKGdyU0ujcME3jw4NzTkSMm8zsnMu5MFdE2DERZSlclrzZBAIIrHGd53ldVxPVG10j9uzEOSEnVF6PmIApOOedY2bvvWcvIjHGlBO78lO4XcSRnXPkyFxNEQCCI4eIWTLNVFivpvCj9/hTE3sfaBxxmkRWBzADr2GCMHofkMh0v3AYioKnnCSWF5L1JwllAgsEAkiF9QhoWu&#43;cgwBvXug2cfCep/Hxw&#43;/x3bQT&#43;t81PyNPPI3sHRcPg1TcCiJlKhpdnD4IEJAU3YcNJVk8brwmRwFCpJiVftwGf3YqoZYf/4uGkZASusc1ccx&#43;GMIwOnXxKgEUABSs4AfwtlIXcWl8BqwW8GYE14jdOIEPBeEgjkicJanCevakLihD1sCq2m0/N&#43;kAhwAtJHyXjfyqxBwGdTGmsBVT5sLrI6TfEA7CZzBURAjo26z8lRD7EAztVKcBkFKOcV3X9c11fAfiEMIwDOM4msOxxGqe5/1&#43;H2P80ct7/VQF4L3XYJkQ0TEDlOBpIvnRK3zlxOM4DsMQQhCBnJPBF0Ky5OtNAN&#43;a&#43;P379yEEiwHGbhFZ19V71hTsjb4t4X6/J6W&#43;tGnZ05v6fwfCnC&#43;ouVnC19&#43;8/nOliH0/ffEXf35iANygv3RM/6qtKqPwkF6ccfCLWXnji0f18F&#43;HWJeeYowpRSvaWCGB6GsSKCRC0jRYY/uhGlFSs87dXSO7HpUMkrVfDSif94LaI&#43;z&#43;jb5iF9&#43;DuNaSc1qWeZ4XQ5&#43;hkP9SGZRd51zAlBVTrTFgHYXWVOj5a2Tss4&#43;WZbFyt3NOc8NCRGS/NkbbR6Yx67pa4hI0tWRmAxc/uQzYig4qgwJ&#43;1nV1ziGh468q42j5DnIurHx6enp&#43;fjYG2f1DCHZZn2rknBFxGAZEfHp6WpbFex9CIKIY47IsWjlhIqrtIGZEbDec53m324nIqDRN04cPH5oIf1pi&#43;weRnOMQRue4vCYWwZRA/Th8piDkJIQYrDILUF&#43;XLnqSGKPpMjOb6TSbMMnZF1nzxJRSE8a2BaujpHVdSzrp3C8Bo6sAiqcWJzgilT2vCWNRR3AEnsG5z5VBJSIMwT8&#43;PjKzFZcszJjyngAk45ep/MPDg/mTYo6IJjkTnvfeZGCBSkScc5rJ52VZmqfy3o/j&#43;JP7n4MAcsZlwd0eczZhaIcLIXh4mGAIQHQkA3t9z&#43;6c42EA8xWtM2MW4LTrSVvfzUhZhhYJtmtrgE0xZhHzS/bFFqUa1mpW9W9Aie9BmwAEYsJllZSqACw9iKm4k5iKKeDWfEQEZuC7zcIUf11XC632JhENw9C0OMZYMMCyaiSAFp97kGNBoqm8yeDn1/HbxCe/iyp&#43;URsqr1OClz3MCxCqEWiG4AjGAXC8KzzUBoP6kGYECFDchOOGUFNKesGSUhEAkSOy&#43;RdB2ACstp3V77&#43;eLJ1Nq4nAexgHTFwzJvNF6g&#43;qO6rJ8aaRcD1dw/q1lo6hQdvOsYAF2&#43;YiFMkMFn71eod4lJC3VM4wj8WGX139iwBQd8WOphG8bt8&#43;OOy97VHQ8A2ROIfMxSwu0saamkMZus1ZU7Di7iwDIMP7zb003&#43;K9N/5eVPPm3F8B92spQpURVEk/2fGt9Cnng8Ymw4UFoT8/Z&#43;VvsS2Ft0QOoFX9slobGjqyGHuDv69D943YIIMq1L/pVc0Cqnkxh2EoCk7O5iRaCmaK30fm18TcewhNAe3/nyxp3c8aczIaSwrCaVWmwyOwC66dq2nY9Pxx/dpejZDY/EBKq6U55wLAzUYcmQ&#43;/a&#43;eaWhd&#43;ttz1fjrJ0V438VYGSPv9ft7POasMNg&#43;&#43;uXti74fimu8qEG2aXmfqrs2p377VqwGat4k3qFehekxxA4JaJlNh1OBZMOK9iWUfUVpJue&#43;yNSTzBZr&#43;mozDnAOaoxjGwMmQeNNcsJKO1hRLsIDPxH/m6q1QM&#43;uoi6ijH5RMtN9sd78AHbxza5m016agItkq&#43;ZISes/Bg4KZO8lq3QZGP378uJ9n0XrO4&#43;Ojc&#43;7G5Mu14zevTGAmgOp/5nm2OGw5sKmnlZERwKsVFITuPlmHw55pVosud65pNfR14/9XIbdRSyePXdAwZiuEKqvMR5vvRgB2zodA7OlTFmDyM3uKMe12u48fP768vDSgZQ2Wp6enGzGgb0B&#43;7pBkE2pvRn2h9Mqype9d2//aiML51&#43;9c2MXmqGd&#43;9/79w8NDdUFETsezxtNp3GP07azAe19H117M8/z33//zn//89263OwRhjQpRsa&#43;V484XbeUjK/RbMUNUOz5JrdZthQ0i6htB9r5hPDvVsy0arBpoIcquaRrQfZ0bFDQ1uj2&#43;ZhvxuhNCki35n6bpzz///OOPP7hjWm1mHRVh5GhXWQNCrykmWh2oEDmcDDjEkpTSy8vun3/&#43;eX5&#43;tsNkgHX2ay7Id&#43;6Tj37dzGy9RZH8snvZz/uU8j3DMq2t5kOYxpGZU6q17sIL7Sc4qphC12ynrPIyLy/7l5xzazlsNas8z/O6LFh0oihqbVzP8/Nu19fYLy7GOTdN0zhNrJlRVDk/vHv3&#43;&#43;&#43;/xxhbQ0aWZV7X2OZz&#43;37h4cCFrbQWD6SOMW7Xb0VNbyc77KumyNM0ncBQ7fuHSdXnmgV4Hxz7nMW5MEwPhV21PiitM3FujVaHcltfzCwghKFZgH5Cp&#43;FdYAjDOI4Cotew7tncgAzD0BqiDTt4HStvFmDHQ2E7KdT3hfrZQ&#43;PDu3cP4ziWtH9T/xzjutvtYoz1OIbulZAcu771IdsJAgDBaqQV6hhk0uWRhVtE9N7/9ttvMcaHh0kFDNoypN6GrugOpUzrisVCVOy5PgXsKCZqu5SdnHe9zt1uc4mmBBfrqZsqoAjGhCmDI2AW2gzaImI9NCeg0CJnyVgLL9Sebdppj&#43;jTnbbrYRg&#43;fPjgved&#43;xbSdwdiK0OjY2ex0V4Q4oNVz3qntW6W&#43;rsQM8PHxPQLMS/GwrH7UnmgKdVEGIjgv&#43;PyCMcqWTGz9IjKQBmOQYRB3FpWuQVgLJzbw0ncjTsQXE&#43;73sMbC/XEQ6/21YDDPc1ZEV4WhdVw1qq2uqbdt9ne&#43;GNPLaZqaBaA1CIvSbeI1A3eOvb/KowtrP&#43;CHwzvWbJmmxN73FmACuFZ5FkFSvBt1OENPvG4C0LM8RDAE8F6ILoCyi&#43;0E0amkZgHXBMBJeZQK60M4EjCrgeecdD31LJGpP5oANg02GdtQzMlies3jbfAE25RHjFmjk6j/MZf35dXHGhJVHfTca8vvqoO6Ll094yfAqe7/YAFqjc5B8MUFXQwDlePHMhDJdgyLtl1dfK7JxakL8h4cSWOinVfM2bXT0VvpDA3ktFTntgUY0Cps71Zc/ktJlnlZ4ypScq4BUOPV12ZJWSSmuCzlts5RrxfYFf56LthqY4IYC8fFBJCL6zcXhLWZeiqAa0Vszf5qFGje&#43;fy525AO1LEofUQbzjA8mrSgIjmXECDGULWniiKhDe6dRJoWA1pg4JO1ivq1GJNF4G1o9&#43;upZS2SMxKd/q2PM2XcZlIAjnrDHWy3YWKR0wDQNnmm/u0WcuO5GvFR4NCW1UfY&#43;1skl26gue2uglFq0rpW05WODgLQ4IbseZSBNTirEV2LVJ9BBi108g7NBfX9Fhs2ufStwohifnx4Z1un2JSG9tfgooZcjAEVsOtHKWW60tTOalRObUsypOOo1qL3iVuv6rEBngY6L8aA9k5vAZa/4AlG/IKKcTcVcRhLsXpQBbeAFrtu3EFKaorzAicDhjrAIdrELvqpeOT86Yc99wahKfFakGMJkKIVlgvPzRnXtXi/EmD80WBgC&#43;CfPLVgmPXEHHubs08PtaBtJE3g9GTvF9TrD0EF6t&#43;6yZa4l6CqiUUDAxd9sSWCKRUsGNejJTiNAcJybVm9Xzh50xTfbE494YVUQNu0khKu&#43;lcx&#43;jzjRLT36OX5Yk78HrfMUTPyf6sJdbCcOh9BBc/KpkEtCDcwcPEm3uOYIXF12zW4oXiPzBWcXHv8ybb799vg&#43;7XnMmMQICeWiNl4dZ&#43;H9lfXSZ7jW/Vu4yIKOgrC6q0ue9IvJnODLR/uz0xsBYwjAZzzQqEIbpvvuvYKDb25oFsLuBIDRLL6kBujXSmh0wkFcuAY&#43;jzAZoRzslhSgFDr4DaNAx3A6e9/vphWEOQbZdVzgHE34ca15u8KgFsPLsjCMl2HoRWJFhcUq2u2fNh7CJfQ59E3r2zbUHyPBa8tPmVYE3pV&#43;pYqmde20qxdV/8WTC0xHZU32tGE88X0gjEB5JSi1RpbgN4qfJv1Y/vmwVZuRlGbiqgXECI7l0smfAhi1xxFey5RSbUs0W0TkRYSbxwjPOH7ib/uM4CLRUCr1xb05VT3oRaJ28VOJwMtTzSgA8cFn/7R54vpa5cWhA&#43;zsTocSNs8flJmHTmyNszcEtqLIXormx6Gvci5MAyOud9na/1fimligGcMcNQEsE5RLf9dNtBr0LsJ4IQLFxIlsDLfIQM/bESL&#43;24DERcsrDs&#43;dRFhtwvsxf8FAAD//8IFbdoR1kJPAAAAAElFTkSuQmCC"
     data-sizes="auto"
     width="2526" alt="List of example sites."
     loading="lazy" />



  </a><figcaption>The listing page for the site manager tool. This runs on our server and can be run in developer&rsquo;s sandboxes as well.</figcaption></figure>

<p>The main module defines an entity for each site, a couple displays (like the listing page displayed above), and the annotated plugin base so we can add new features easily. Plugins can be enabled and disabled on a site-by-site basis, and can be run from the plugins dropbutton.</p>
<p>Once we had the basics in place (like getting the git and drush status&rsquo;s via simple plugins) we started to add more powerful features to turn the tool into a dashboard.  We added the module search block you see on the left as a separate module that searches all sites for a specific Drupal module (nice when security updates are released). The search module also provides a plugin for each site that lists the modules for just that site.</p>
<p>The last site in the screen shot has a reference to scanning the HTTPS certificate for a specific site.  This is another feature we added recently as this tool increasingly starts to serve as a dashboard. The HTTPS Scanner module again provides both a batch job to scan all sites, and a task plugin that scans just one.</p>
<p>As of this writing, all told there are nine working task plugins across four modules (there are a couple more underway as well). Tasks can also have their results displayed in a block when you look at the details for a site.</p>
<h1 id="building-your-own-manager">Building your own manager</h1>
<p>Hopefully by now your convinced that it&rsquo;s worth having your own plugin manager at least sometimes. So the next question is how to build them.</p>
<h2 id="elements-of-a-custom-plugin">Elements of a custom plugin</h2>
<p>Plugins have several parts, most of which are small and simple:</p>
<ul>
<li>Annotation Plugin Definition</li>
<li>Plugin Manager Service</li>
<li>Plugin Interface</li>
<li>Plugin Base</li>
<li>Example Implementation</li>
<li>Controller (optional)</li>
</ul>
<h3 id="annotation-plugin">Annotation Plugin</h3>
<p>The first step is to define the annotation for your plugin. This is a simple class that extends Drupal\Component\Annotation\Plugin to define the variables you want listed in the annotation comments of the plugin. This file goes in custom_module/src/Annotation, and as always the file name and class name match:</p>
<script
  src="https://gist.github.com/acrosman/311d5cdf5beaa9b72efeb310f0ef9bfe.js"></script>
<h3 id="plugin-manager-service">Plugin Manager Service</h3>
<p>The plugin manager itself is a service, but the vast majority of its function comes from the parent classes.  The convention is to place this file in custom_module/src/Plugin (as will the rest of the base definitions we&rsquo;re about to provide).  In this case we call it: TaskPluginManager.php.</p>
<p>The full version of this provides a few more improvements to this class (I overrode the getDefinitions() method to provide some filtering options to support disabling plugins), but those aren&rsquo;t actually required for it to work well.</p>
<p><script
  src="https://gist.github.com/acrosman/0fefd96c7cba5d70b618add5f4fb5f05.js"></script>
In short, this class just provides some general definitions to separate it from all the other annotated plugins.</p>
<p>And since the plugin manager is a service, we add it to our module&rsquo;s service.yml file:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yml" data-lang="yml"><span style="display:flex;"><span><span style="color:#f92672">services</span>:
</span></span><span style="display:flex;"><span>   <span style="color:#f92672">plugin.manager.task_plugin.processor</span>:
</span></span><span style="display:flex;"><span>     <span style="color:#f92672">class</span>: <span style="color:#ae81ff">Drupal\sitemanager\Plugin\TaskPluginManager</span>
</span></span><span style="display:flex;"><span>     <span style="color:#f92672">parent</span>: <span style="color:#ae81ff">default_plugin_manager</span>
</span></span></code></pre></div><h3 id="plugin-interface">Plugin Interface</h3>
<p>Next we define the plugin&rsquo;s interface.</p>
<p><script
  src="https://gist.github.com/acrosman/e002ad2c529820271e09de58fc71307f.js"></script>
For this plugin I defined 4 critical public functions, three just provide basic information about a plugin, but fourth (run) will be the interesting one in a minute.</p>
<h3 id="plugin-base">Plugin Base</h3>
<p>Finally we&rsquo;re getting to something interesting (or at least something that requires code).  The plugin base is an abstract class which all the actual plugins will extend. This goes in custom_module/src/Plugin/TaskPluginBase.php.</p>
<p><script
  src="https://gist.github.com/acrosman/e5458193975e1a6fd5fd1c480caf88f2.js"></script>
The things worth taking notice of here are the fact that I inject several services into the base class, so I have them for each plugin: translation, main sitemanager module configuration, and a wrapper on Symfony&rsquo;s process service to make running drush and git commands easier.  The three informational functions are fully defined here, but the run function stays abstract since it&rsquo;s the function that actually justifies creating a plugin at all.</p>
<p>In theory I could stop right there. I&rsquo;ve created a plugin manager and defined all the things someone else needs to be able to use it. But there are two technically optional parts that are useful if you like to work and play well with others:  an example implementation and a controller for use or testing purposes.</p>
<h3 id="example-implementation">Example Implementation</h3>
<p>We went through all the trouble to create the plugin manager, before you stop you should create at least one plugin to prove all the work we just did actually works.</p>
<p>This is a simple task to use drush to run cron for a site.  The site entities know the location on the file system for Drupal root, and the simple task service handles the extra settings to control where the commands are run (and time outs and error trapping), so we can safely feed that to command and the location to get the response.</p>
<p>The method returns a render array, that can be used as part of a response.</p>
<script
  src="https://gist.github.com/acrosman/fc5c8f6abed2d2efafdaed7fc8134a5e.js"></script>
<h3 id="controller">Controller</h3>
<p>The controller is truly 100% optional.  There are two reasons you might want to create one: to actually run the plugin if that makes sense (it does for the site manager), and to provide for easy testing of a plugin.</p>
<p>For site manager I created the controller because I actually needed it to run the tasks from the dropbutton (its the link used by all the links on those buttons).  But when I created the others for clients, I realized they can be very hard to test. They are buried inside several layers of complexity, making a test suite is a challenge to create correctly. And since are of the reason to use a plugin for a project with growing definitions, the test suite is often of limited use for rush additions. But by having a carefully built (and secured) controller and route, you can create an endpoint to use to test the plugin.</p>
<p>Because it&rsquo;s optional, and well covered elsewhere (or heck just use Drupal console to generate it), I&rsquo;m going to skip over most of the details of actually building the route, and linking that to the controller, and focus on the single function of running the task itself. To keep this short I&rsquo;ve removed lots of extraneous details like security and error trapping – creating a tool like this isn&rsquo;t for beginners so I&rsquo;m assuming you know how to do those things.</p>
<p><script
  src="https://gist.github.com/acrosman/67c42ca4aec9306ea0820e9f1bc29977.js"></script>
The plugin manager service was injected into the controller (code not shown) so I just have it create an instance of the task requested. The controller calls the run function.  If the task is successful the output (a render array) is used for the response, otherwise a simple message is send. This sends an AjaxResponse to work with Drupal&rsquo;s standard JS handlers, but it could generate a full page just as easily.</p>
<h1 id="final-thoughts">Final thoughts</h1>
<p>Being able to create your own plugins easily is really nice.  In Drupal 7 custom plugins where generally considered an advanced developer task. The improvements in the abstraction (and the fact that true plugin support is now in core not pulled from ctools or other critical modules), means that while its still not a beginner task it is something all Drupal developers should be learning.</p>
<p>But remember you don&rsquo;t always need or want the complexity of your own plugins. I&rsquo;ve found the idea a bit addictive, and I get unreasonably excited when I find a place plugins are used or that it makes sense for me to create a new plugin type. For custom work you can almost always do the same thing using extra functions on a service, controller, or event listener, so this is a judgement call in the end.</p>
]]></content:encoded> </item> <item>
      <title>Documenting your work</title>
      <link>https://spinningcode.org/2016/10/documenting-your-work/</link>
      <pubDate>
        Sun, 23 Oct 2016 21:38:02 +0000
      </pubDate> <guid
        isPermaLink="false">http://spinningcode.org/?p=137</guid>  <description>Any project that takes more than a couple hours to complete involves too many details for most people to remember for more than a few days.</description> <content:encoded><![CDATA[





<figure class="figure-figure-right">
  
  <a href="/wp-content/uploads/2016/10/AHC_9512-150x150.jpg" target="_blank" rel="noopener noreferrer">
    
    

    











<noscript>
  <img class="rcf-image" src="/wp-content/uploads/2016/10/AHC_9512-150x150.jpg" alt="Programming books" loading="lazy" />
</noscript>

<img class="rcf-image lazyload show-if-js"
     data-srcset="/wp-content/uploads/2016/10/AHC_9512-150x150.jpg 150w"
     data-src="/wp-content/uploads/2016/10/AHC_9512-150x150.jpg"
     src="data:image/jpeg;base64,/9j/2wCEAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDIBCQkJDAsMGA0NGDIhHCEyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMv/AABEIAIAAgAMBIgACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5&#43;gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4&#43;Tl5ufo6ery8/T19vf4&#43;fr/2gAMAwEAAhEDEQA/AJ/GHiqbSr2OG2bBU5etmDVY9W8P/aVILFfmrgvFGha1Pqlx5Nq8yZ&#43;9irfhHRdet7C8gmidFfhA1GKp0vZ/uzWlVq&#43;1M7Xrm&#43;gUtAu2Lu1RaLrkE0H2HUV3RseGrVuvD3iCW3aJrMtng4NYMngbW/PRhauqjrzXLhtDpxNWpUqHQav9j0zS2fTwgdscg1z&#43;neKZ45jHOvyMe1Pl8Ia6EAEMzL6GoJPBesI3mJby59MVq/Z1ApValP8AhnpEEvmwxyJ91lFUrvX/ALHeiDyt3&#43;1V3TrC8jsYI3hYOqANXPa5o&#43;py3hMNuzD1rmCq6h1sN/BKokMoHHrV&#43;11G1WVGadNueua82XS9bicD7K5X&#43;dWbPTNTjud81rIy8nb2peyM/aM6XUtRhF9NGsqkluBWZ9qnM&#43;xvuN/FXPX&#43;k6xL51xDbSKf4RiqFtpfimVQCJR9Riuf6r1pmlKrUR1N&#43;5ij/dYLfWoLNhAn7yZd5561jv4V8QyBtxkY/WnReBtXkRTMHVv96l9W9oOrVqVDrp2D29qwII55FRSScVGtldQQWtmInJiX5mA4qjc3dzHceQLGc/7W3itaVIRYufG9/bvyFP4VFH8Q7vphazU06bVrmRIFU7Vz1rBkZLSZ47hMOjbWrUzO6i8d3ch6Cr8fi2eUcha5GwjimA2p16VtRwpEPnjoNDZHiic8YFS/8JHOey1lxxRbN&#43;yrItcjISsvaAW/&#43;Ejn9FpP7fnPOFqsbTEfmFMD1pojiI7UDLZ1&#43;bHRaj/t&#43;f0Wqhe2VsFlB9CamECMuVTIPpR7URL/AG/P0wtH9vT/AN1fyqsbTB&#43;5S7EDbcLn0pgWP7cn9qP7cnPXFQ/Zgf4aZ9kOfuUAWP7ak9FzUR1mYn7qn8KiktePuUyC155FIDF8CQS3FzcbHP8Aqx81c/rhlF7dxC33nzDlq6P4f3Pl30sf96IVQvOb/UV/2jWr/iAaej6ZLFHZSMAoIy1bkqiKeN3TzEyflqpp7N5UXBxtWr90QBHvz1PStcN&#43;8pmeJ/dnOQayZPE72RXES8hcV6PawwtBAwQZcDNefRabYx6o18Hbz24xXf6PmTTo3/u0qtIxpVf3hH4ht0j0hhGuAp6CuQ0e68iN/Pi8w7vl4rtdaPmaVMgribfIkdPSilSHiatSmYGuI934h226sMrnaDivQdAntxo8IlC70G1s1wuqCdNfjaAhWYYqrB4sk0&#43;G4szB5jxklmzWdWl/z7OnC1ab/iHdarqUEU&#43;yMdq4&#43;UC/1B2EkkRHdWqOy1xtV1GDzoAisnHNaEKxRajMhTjbnNZfvKZ7FOlh/Zfuy5o1jJJJ5322Rwn8LGuhGK4vTfEllY3s1rK21XY7WrpoJw4DKcg9DRUPNaLxi4NVkABNWUkypqsh&#43;Y0COL8B4Op/9shUV/H/AMTLUSjdzVr4eRJJqp/2YhWP4mvZrPXLxIUGGfDV0s5kdtp3/HnAd4&#43;6vFT6uSIo8etQ2cRj0e2nKDDKvejVpcxRY9azwQsfsVoVDQxvj591dToM0n2Xyt3G4iuRjuRHboDx81dhoEAaJmD/AC5z9a6qv8M83DfxIGhdxE6fcn1WuEgf/SOW6ivR7vH9lzKP7hrzFJIxOA3zdaVI1x9zE1i4k/tCOUH7rYpNE0a11F7uWfdvJxwab4giME8Mn8DtWn4awt7cxjoQDXNjv3f8M7sjXtP4gsumW2nz2rQqVwcdaluMf2mylsb4zzVnW8JBHJ/daql/j7Tby/3l/pXLf2lM&#43;l9l7Mw7zRLRY2uRLvkBztFdVoMm&#43;wiB7Vy95qDwsY4wg&#43;gre8MXPm27b/vZp033ObFUv3dzp06Gq6feNWE&#43;6arp941oeYcj8O1aHWMuGC&#43;VWP4nh8zWr5vM27ZMgetek/2/4V0yTOBE/wDsis2fX/A15O8koDO38RWtbP2ntDJfwyzC/wDxTNpx/AtZ&#43;p3KRwq59a2Y/FXhN7ZIBNhE4AxUcur&#43;ErlNjyqwp4d&#43;zdwxC9ojj/7Tgdf4sZ7iu28CalFeW9zHGx2oR96qiS&#43;Ds7FZK0bHUPC&#43;mhxaTIm/71aOqc1LC&#43;zqe0Osk8s2kmT/AAmvJLTU9Og1&#43;WC5Vtisctjiu2bxFojRmP7cMH3rHZPBzSmRpVLnq2ay9qa1aXtDC8Xarod3pYhsjmYN8vFZ3hUSw6i3mMDuTPWunktvBU7/ADyr&#43;dOtf&#43;EQsJ98VwA3u1Z1f3iOnC/uqhV1pfOsHHoc1mSt9rtbVU&#43;&#43;MCuml1DwxLG6Pdr83vUMT&#43;GQUMVyMpWVKl&#43;7PTeOp&#43;0OC1C1aC9ZHBzWx4cM0M7ZUhG9q6W5vPDbP508qbh/EaQeIPC8I4nStPZGdXHe0p&#43;zLkcnFKnWqH/CXeHo/wDluuPpUsXiXRbg/uZdx&#43;lOxwnnV6PtM26TDb3K1Y0/RrdopyYf9XSXQjEgH8SzGtiwO5rlN3ysK9KkcBA/h23hsop5kKq/3dvei30ixd/JVgD1561vajfLa6JZFo/MX7v0qvYW0E&#43;ycph2Ws/ZHTSORmih/taW1SIll461es9JjvJZExsKYyS1VbiMW&#43;v3LZUoeOvNa/gaCPUNWuAwcIP4Ca0qUv3YVP3Y9vDcNvtdnDbzjg9KX&#43;xrNFwcZJ/vV3j6dbTx5MJBjPc1z2raZbx3L7EI&#43;X1rhRrSpe0ODu5LQzpbrBtLPs35rYPg6N1UGcbiMht1YgSEX8ETQtzcY3fjXrsWk2xteUGFAIrSoZ9TyeXQZbe4eAIH2/xZro9P8FxXFgs7ztG7Dsa1ruxDalMkeFIUE81tWEe3Rt69lNc3tD0qlKn7K55t4n8Oiy05sz5bGV5615/bmQNggvXtGuWMWqaTb3EgJK5WvI5ofsWqyRYIAJFdVL&#43;GcFWlb94TRXUCMgMW4e5rotPu4opk2wBB/eBzXOSWcbxRMo&#43;ZmrqNE09Yp33LldtA8LSqVR15/wAff/bU1r2I/wBInH&#43;yKyL041TZ/wBNa27bi8n/ANwV00jh/wCXZ1NraxTaTbeYgfr1qiF&#43;z3DADCc4FX9PJGkwfjWPLLI&#43;oOnatTTC/wC8HE3zLH4hupmQlR37Zrpfh/KP7YnJ4BWuY1a/az1mdHAZM5x61a0C/d7i4khym4DpRV/hG1T&#43;LY9gDqsTANkk965/XJP9Ib/drF0y8uW1WNZJGIx0zWprD5nk4/hrx6WtU9bC0vZ1DisjKO69LkbW9Oa9Vtz/AKEcv8zAYrxm7uyJEhBx&#43;/rrorq6DIBKcFB3ruq/u0ecqXtKszbnUHUrpiQFEfzGrFjcSf2Gwjxs2tXK2&#43;ptDLciQFy42811Gk8&#43;Hs4/havMp/xD0cTStSMywuvtGhyof4HNeZeJxt1cSgfeFd74amE5vYPUtXH&#43;LLUjbL/dbbXfTOb&#43;JhjKOZLWDHHzV1GjJMl1Lvf5No71zcQY2MTejVv6MS11cIW&#43;XFaGeA/iDbwZ19Pm/wCWo&#43;WtjUTJFqO2H&#43;4M4rV/4QS&#43;uNU&#43;2FgqBw22te98Jz3Fz5sTbfl2mj2p5lSl&#43;7KUWqw2Hh22lvJdo3ECs430N1cNLFICjDO41rat4Mkv9GgtBLtaNiScVmL8PbqG32Lck/LjpWvtTTC/u6ntDzzXVFxrEmJM&#43;9a/hKEytLCjAt7mt1fhndLIZDMHJ9qmh&#43;Ht9aK5inw7ela&#43;1p&#43;z9ma1Kv732hBrAvdFtvt8Lx7lwMAg07T9Vm1HTY7meVTKynOae3w91G5G2a5OD25NQXXw71NAqW8&#43;UUY5yK4adqbOmnjuSpznJ3hEl8qqGyZuvavQrfS5tsb7ouEx9&#43;sqD4faklr5Uky7d2c&#43;9TR&#43;BNXDMHuzg&#43;5rTE/vDnp1qlOrcbNYyrcugAc9cpzXXaMAPDbKeG2sOareHvDb6ZC6TyNK79604NNeGFoipJBJrh9kelVxXtKR5t4Xe5tPEsyOD5buan8X2sSGdCRlvmUV3MGhRQTmbyxknNYPinwneaxepNA4RVGMmuqmcNOr7NHmx8xdOj&#43;XjNbGiSjzp33heB1rXj8D6t8sbsnlKaW78Aagf9S4XNI1w1X2f7w//9k="
     data-sizes="auto"
     width="150" alt="Programming books"
     loading="lazy" />



  </a><figcaption>Your documentation does not need to look like this.</figcaption></figure>

<p>Early in my career I spent a lot of time as the only technical person on project, and therefore believed that I didn’t need to document my work carefully since I was the only person who had to understand it later. It turned out that if a project was back burnered for a few months the details were pushed out of my mind by the details of eight other projects.</p>
<p>Any project that takes more than a couple hours to complete involves too many details for most people to remember for more than a few days. We often think about project documentation as something for other people – and it is – but that other person may be you in six months.</p>
<p>I learned to start keeping notes that I could go back to, those notes would turn into documentation that I could share with other people as the need developed. My solutions were typically ad-hoc: freeform word documents or wiki pages. For a while I had a boss who wanted every piece of documentation created by IT to fit a very predictable format and to be in a very specific system. It took two years for him to settle on the system, process, and format to use. By then I had a mountain of information in wiki pages that documented the organization’s online tools in detail, and no one else is IT had anything substantial. It was two more years before the documentation of other team members got to be as good as my ad-hoc wiki.</p>
<p>That’s not to say a rogue solution is best, but the solution that I used was better than his proposed setup for at least three years. That experience got me to think about what makes documentation useful.</p>
<p><strong>Rules of thumb for good project documentation:</strong></p>
<ul>
<li><strong>Write up the notes you’d want from others when coming into a project:</strong> think of this as the Golden Rule of documentation. Think about what you’d want to have if you were coming into the project six months from now. You&rsquo;d want an outline of the purpose of the project and the solution used, and places they deviated from any standards your team normally uses. You’ve probably read documents that are explaining something technical to an expert that are hard for anyone else to understand – if I’m reading the documentation I want to become an expert, but I’m probably not one already.</li>
<li><strong>Keep it easy to create and edit while working:</strong> if you have to stop what you’re doing and write your notes in a totally different environment that your day-to-day work you will not do it. Wikis, markdown files, and other similar informal solutions are more likely to actually get written and updated than any formal setup that you can’t update while doing your main work.</li>
<li><strong>Document as you go:</strong> we all plan to go back and write documentation later and almost none of us do. When we do get back to it, we’ve forgotten half the details we need to make the notes useful to others. So admit you’re not going to get back to it and don’t plan to: write as you go and edit as you need.</li>
<li><strong>Make sure you can come in in the middle:</strong> People skim project documentation, technical specifications, and any other large block of text. Make sure if someone has skipped the previous three sections they can either pick up where they left off, or give them directions to the parts the need to understand before continuing.</li>
<li><strong>Track all contributions:</strong> Use a system that automatically tracks changes so you you can see contributions from others and fix mistakes. Tools like MediaWiki, Wordpress, and Drupal do this internally. Markdown or text files in a code repository also have this trait. Avoid solutions like MS Word’s track changes that are meant for editing a final document not tracking revisions over time.</li>
<li><a href="https://commons.wikimedia.org/wiki/File%3ABe_bold.svg" title="By Be_bold.png: Helix84 derivative work: Oile11 [CC BY-SA 3.0 (http://creativecommons.org/licenses/by-sa/3.0)], via Wikimedia Commons"><figure>
  <a href="https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Be_bold.svg/256px-Be_bold.svg.png" target="_blank" rel="noopener noreferrer">

    
    
    
    
    

    <img class="rcf-image external-image" src="https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Be_bold.svg/256px-Be_bold.svg.png"alt="Be bold"  loading="lazy" />

    </a>

  
  
</figure></a><strong>Don’t fear editing:</strong> follow the Wikipedia community’s encouragement to <a href="https://en.wikipedia.org/wiki/Wikipedia:Be_bold">Be Bold</a>. You should not fear making changes to the team&rsquo;s documentation. You will be wrong in some of what you write, and you should fix any mistake you find – yours or someone else’s. Don’t get mad if someone makes a change that’s not quite right, revert the change or make a new edit and more forward.</li>
<li><strong>There is always an audience:</strong> even if you are the only person on the project you have an audience of at least your future-self. Even if it feels like a waste in the moment having documentation will help down the road.</li>
</ul>
<p>Remember even if you are working alone you&rsquo;re on a team that includes at least yourself today and yourself in the future. That future version of you probably won&rsquo;t remember everything you know right now, and will get very annoyed at you if you don&rsquo;t record what they need to know. And if the rest of your team members aren&rsquo;t just versions of yourself they may expressed their frustration more directly.</p>
]]></content:encoded> </item> <item>
      <title>What I learned by getting a degree from a liberals arts college</title>
      <link>https://spinningcode.org/2016/10/what-i-learned-by-getting-a-degree-from-a-liberals-arts-college/</link>
      <pubDate>
        Mon, 03 Oct 2016 00:47:25 +0000
      </pubDate> <guid
        isPermaLink="false">http://spinningcode.org/?p=123</guid>  <description>The most important skill I have is the ability to learn new skills. And I learned that by getting a degree at a liberal arts college.</description> <content:encoded><![CDATA[<p>I have a degree in Computer Science, with a minor in Economics. But I earned that degree at <a href="http://www.hamilton.edu/">Hamilton College</a>, a traditional liberal arts college. That meant I was forced (now students there are just encouraged) to take classes in a variety of disciplines. I went to Hamilton in part because they offered degrees in Computer Science and History, and I was interested in both fields. In addition to the courses required by my major I took classes in history, religious studies, philosophy, art, and more. And I learned critical skills for work and life because I took those classes.</p>






<figure class="figure-right">
  
  <a href="/wp-content/uploads/2016/10/IMG_0202-300x208.jpg" target="_blank" rel="noopener noreferrer">
    
    

    











<noscript>
  <img class="rcf-image" src="/wp-content/uploads/2016/10/IMG_0202-300x208.jpg" alt="Two hand made mugs" loading="lazy" />
</noscript>

<img class="rcf-image lazyload show-if-js"
     data-srcset="/wp-content/uploads/2016/10/IMG_0202-300x208.jpg 300w"
     data-src="/wp-content/uploads/2016/10/IMG_0202-300x208.jpg"
     src="data:image/jpeg;base64,/9j/2wCEAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDIBCQkJDAsMGA0NGDIhHCEyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMv/AABEIAFkAgAMBIgACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5&#43;gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4&#43;Tl5ufo6ery8/T19vf4&#43;fr/2gAMAwEAAhEDEQA/AMv4Ya49mZLf8a9jtdTFxGPlr5v8F3X2bWkBP3&#43;K930xv3YOa4av8Q7qf8M6dpN1ufQivKPG&#43;nxlxLvPXFek/av3Wz&#43;GvP8AxTKLmY2iLuk3ZrqOFnnX2TzJcJuapRo1zBMtw0ZCVuWenPa3kglXDY&#43;XnNTz6sYLN7eRMj1xXLiatSmOjuQadMAeoreiug0flqcvXJ2kSSB5DIF7irmmXJt5fMkJI7Gsvan0n1ZWLOpaHcPIt443Ac1iNr9pa74pICxBrotS8UKLN7cMDuFeZ6k2bonPU11YaoeRiqZtvqi3chMUAQVEZdgL7/8AgNY6sXwkZwalTzRJhsba0q&#43;zOE2oNTtI9OdNn74N96sa9vTcydKhuO&#43;KLdeDvNc3szMEBjU/7VS2c08ZOw/JURdz8iFTUaR3Yk&#43;Vd3&#43;yK19kAtoTa3UckbfOpr0Ow8aX9vCAIQ/HrXnYU5zit3TpcBc0VTppnZHxxq8sZRII0z361mrdXlzfJLNJudj/AA1Pp9lFNH58j7Ep14LS1Ec9nc7pA3K0KqHszo7Hw1qGpSiRsQx/w571tXHhJfsLW8yhvfFZuj/ES3tLeOK9tyzJ/EK3j8QdJuF&#43;VD&#43;VaOrTYKlUOLufCkNucJuFUtT8P3QsxFbSduOK6668RWl3l4V3fhWVc6lJNC3lJtAHU1zHdUqv/l4eZLFcx3DpKpLJxk1k6gCbvA5PtW7c3z32p/Zo/kLvtL1694Y&#43;Gmkx2iT3aieYgH5q6aSOGqzwW1srqSQGGCRj7Cui/sW5kjQmBwxHPFfQ0WhadajZFZRqMf3ayr/TkXOIEA&#43;lFUKR88y6fLFK4Mbce1EsHlWzuRivZ5tPtyTm3Xn2rN1Lw1YXNm&#43;6JQfbiuY09keJpFJncjH1qzDqc8B&#43;aMN&#43;FdHrGgyaOokA3QN91v6VzJiLMTg13Ujhqm3LaDf8gFMH7qQVYMoqtLIM1ynedHNKf7IieL7o&#43;9isdrjEgftVjR9ShUPaXJ/dydKratGbR8fwHoaKf/PsEv8Al4XnMckQeprQjaMVl20vm2q1ftikcic1xPc9JHTaYr&#43;cAEG01vNo9zPauBtUEVzunO00xZHOxOTVLUvHlxDK9tbH5U43VqjmqkeqeDLrSQuoErIN&#43;fl/hrrvDXjx7FViuRviAxu715&#43;vi&#43;/u99vO&#43;6N6hjLKuRnk1pUq1EFKlTqn0DZeL9K1BfklVX/2uKp6tqKSf6tg3&#43;6a8SW4liwQxFWItRut/wDrX/Oj6yH1X2Z6I63MrAgHGfWprlAIQJJFT61xNtqFySP3jfnWg1xK6/MxP1NBnUM/xxextp0VhAMqDuZveuEgTHU1vXVzNfajJFHGW2nHNWGsordQs0S769fC0v3Z4&#43;Jq/vDmZJk7VUlueOKXypDVWWPmvMPTFjmO7LH6VpjU/OhFtN84PRj2rGPHWo3lIPFAe0OmsY3QMgORmrcZImZfSsHTtX8v5JK10mVgZEcc1z1UelSa9mb3h&#43;9WWaa2ztLAgVxWqRy22qTRPnIcmrdvqP2G681H&#43;dTWpqstr4jslmg2JeIvzf7VaUkc2JOcWXDBxXS2sgkthn61xpaW1fy5Qd2a34LrZClGJM8KapPmfdq1bR81jC62SVs2M0cvesjtdU2La2BHerV4Ut7GSTPKrTrLZ5dY3i69MGneVH1c1rSPNq1Tk0ubhr5pYXbcT2roNO07U9YuooNrbm4yRXMWUkivlW2vXtPgCe7udAkFtAjXKH/WOK9em7I8erueKmXy3NUpJMy7qvRm3SNw53PVUxxpzuGPrXCemRSR&#43;YM1VkCR1a80Z4Py1FLGJKBFCTO/IpUu7gfIjmpZYSO9Nji&#43;dWFA7sryXMrn5&#43;tTWt3NbSb43Kt6UksX73pTfKfdlaYXNSa9W&#43;Qb0HmipopD5YHpWD5kkUm&#43;r9vdZTmsqtM1pVTUEua39F5NcvFLzXV6GRwxxWRrUZ11iP3eK5HxRegaisXULXYCe3trcyOdpxXl&#43;rX32nUpHHI3cV00jgqliPmXKJXr/gnxJpvh7QG85y0rHJUCvG7eVwnyruq39vaMfdNelTSaszhqGLLH5bkZqI/Mmyprn/XNUMP36849Ij&#43;zmP8AjqSM9gpY0XFJDQZhNuUbZMFj29KW1t8yDnbTZP8AWGpoP9Yv1oAluLUecKjaLamwIB9at3H/AB9rTNS&#43;8n0oAyZY0&#43;6VqHy8fcqzJ0qM/drQBYnkHatm01CWHGBWfB/qatQdVqeRFc7L95rd3ONr9KoHYeSnNPn/ANbSP0qjEb9p8occVJHdCRKo3H3abB1rUR//2Q=="
     data-sizes="auto"
     width="300" alt="Two hand made mugs"
     loading="lazy" />



  </a><figcaption>Making these mugs made me a better developer</figcaption></figure>

<p><strong>Ceramics and pottery taught me to be a craftsman, accept critical feedback, and admit failure.</strong> For all the courses I took in college, the time I spent in Ceramics and Pottery studio taught me more about how to be good at what I do than any other course work (including CS). Basic pottery is a craft that doesn’t allow you to save a piece once you’ve made mistakes. If you are making cylinders the walls aren’t straight throw it away and start again. My professor in those courses set very high standards and pushed us to meet them through brutally honest, but helpful, critiques. He taught me to appreciate honest feedback, and to be skeptical of my work before showing it others. In those courses we had to do good work, recognize our weaknesses, and hit deadlines.</p>
<p><strong>History taught me to care about communicating.</strong> Between the courses I took, and dating (and eventually marrying) a history major (now professor) I came to understand how important it is to communicate well in speech and in writing. I started this blog in part because I wanted to make sure I was spending more time writing to help maintain those skills. I frequently find myself in meetings having to explain highly technical issues to non-technical clients and colleagues. Knowing how to adjust to my audience, without insulting their intelligence, is a critical part of my day to day work.</p>
<p><strong>Religious Studies and Philosophy taught me to make a well reasoned argument and express the ethics of my position.</strong> Religious Studies and Philosophy both require you to make arguments based primarily on the strengths of your ideas – you can’t research your way out of a bad concept. If you cannot assemble a well reasoned argument you leave yourself open to easy counter attacks. When you work at a nonprofit you often are building your ideas and your explanations from a mix of the facts of the issue, your worldview, and the assumptions you are forced to make because no one has all the information they really need.</p>
<p><strong>Economics taught me how to view the world through the lens of money and trade.</strong> While I don’t think it’s the only, or even best, way to view the world it is a very important worldview that dominates the news and political spaces. Understanding the strengths and weaknesses of economics as a field helps me think about plans put forward by various politicians. It also helps me think about how the companies I work for function, and helps make sure internal systems and plans I propose have a strong business case to support them (and because I learned to throw things away in ceramics I also know that when the case is bad to stop and try something different).</p>
<p><strong>The most important skill I have is the ability to learn new skills.</strong> Since I graduated from college the technology I work with every day has changed several times. I’ve had to learn tools, techniques, and strategies that didn’t exist when I was in college. And my career has evolved through multiple employers, disciplines, and areas of expertise.</p>
]]></content:encoded> </item> <item>
      <title>Bad data systems do not justify sexist your behavior</title>
      <link>https://spinningcode.org/2016/09/bad-data-systems-do-not-justify-sexist-your-behavior/</link>
      <pubDate>
        Sat, 24 Sep 2016 21:07:10 +0000
      </pubDate> <guid
        isPermaLink="false">http://spinningcode.org/?p=118</guid>  <description>Just because your database cannot handle modern families does not make it okay.</description> <content:encoded><![CDATA[<p>This week we get a letter from <a href="https://atlanticbb.com/">Atlantic Broadband</a>, our ISP, addressed to “Aaron &amp; Eliza Crosman Geor”. My wife has never gone by Eliza and her last name is not “Geor”.</p>
<p><a href="/wp-content/uploads/2016/09/IMG_0177.jpg"><figure>
  <a href="/wp-content/uploads/2016/09/IMG_0177-300x200.jpg" target="_blank" rel="noopener noreferrer">

    
    
    
    
    

    
    











<noscript>
  <img class="rcf-image" src="/wp-content/uploads/2016/09/IMG_0177-300x200.jpg" alt="Picture of an envelop from Atlantic Broadband to: Aaron &amp;amp; Eliza Crosman Geor" loading="lazy" />
</noscript>

<img class="rcf-image lazyload show-if-js"
     data-srcset="/wp-content/uploads/2016/09/IMG_0177-300x200.jpg 300w"
     data-src="/wp-content/uploads/2016/09/IMG_0177-300x200.jpg"
     src="data:image/jpeg;base64,/9j/2wCEAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDIBCQkJDAsMGA0NGDIhHCEyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMv/AABEIAFUAgAMBIgACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5&#43;gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4&#43;Tl5ufo6ery8/T19vf4&#43;fr/2gAMAwEAAhEDEQA/AOy6VVvL6OziaRuWPQetWHYKhYngDNcb9rOr&#43;I1iz&#43;5iPSikB0K6iVRHuEwj/pV1HV1DIcg&#43;lQXMEc8DQt0I4rm9K1R7DUZNOuDxu&#43;VjQB1lRVn6xemy0ya4U/NtwK4qPVb&#43;80aQfapI7iF&#43;TjGQazND0TIx1qPPNchp&#43;oyWesxQXd2zQtBu&#43;f1qCbxHP/wkuyNj9kztPHGaAO2yKM151LreqRzNExby5Z8JJ6DPSrgfUIb27BvHZY49wFAHaGiuLGpXd9cQQS3n2aLyg289WNdVbZFug3&#43;Z8v3vWgC1RUVFAEtFRUUAJrl19m0yVs4z8tcr4ZjuHunlhwcnBY9q7W9soLyPy5l3JUVlY2&#43;nIyQpt3Vp7UzCS3l8riU&#43;Z61wuum4i1BGuMJID8rDvXofbrWZqOmW&#43;oshmTJXvRTqGhWjvIbjRY5LgjZwGyO9Oa2tWVXUREScA461MdMt1s/suDs&#43;tOWyhijjjVc&#43;X92swKctjZ3Mm6TyWeMY5HSomhsorcvmLy85zjvVsadCssjKDuf73NNbS7U2i25VtoO7rQBVaO23RwN5XzfOgIqXy4Xkl&#43;eLeF/ecdqnfTYGeKRuqD5aj/s6ATyzYO6QbWoGVJre0kgV5PJaJPunHStS1eNrdGj&#43;4F&#43;XFVf7NgNv5B&#43;5nNWo444o1RPuLxQBLRUdJQIloqLPvRn3oA1M1EetGaKADNRd6M0UAFFRUZoAO9FFHagYUlRk89aM&#43;9BqFFFRUAS0VFmjNBkFGfeioqBGpRRRQBH3pKO9FABUVFFABn3oJ461FQTx1oGBPPWjPvUXeig1Jc&#43;9FRUUAGaM&#43;9RUZ96DIlz70ZHrUWfeo8mgRuyVFUslRUAFRGpaiNABUVErCKPJ6U1Z4mRXDDa3vQA6oj1p/mJzhh&#43;dUzexyI7L/C2KBlmioUuInHJApj3KKeOaDQkNFN8xXGdwH40hkQfxr&#43;dBmPqM9aa80apvJB&#43;lRJcK5&#43;8BQImopAwboQaWgDekqKpZKi70AElRVLJUVAEVygePaehrMk0mI4TzHC7s1qzdBUUn&#43;soAoxWSRksGYnpTYtOjCSAO3zHNXB90/Wlj&#43;41AGXLpiH/lo4qUWaxxKgdvrVmShuiUDM6TTlbcPNelbSojtO9/zq433jUjdBQIom0SKFwCeW71W/s1GbzPMcGtCb/Vt9aav&#43;rFAC20CwRvtJOfWpaVP9W1JWgH/9k="
     data-sizes="auto"
     width="300" alt="Picture of an envelop from Atlantic Broadband to: Aaron &amp;amp; Eliza Crosman Geor"
     loading="lazy" />


</a>

  
  
</figure></a> It’s been this way since we signed up with them, when we ask them to fix it they acknowledge that they cannot because their database cannot correctly handle couples with different last names who both want to appear on the account. Apparently it is the position of Atlantic Broadband that in 2016 it is reasonable to tell a woman she cannot be addressed by her legal name because it would be expensive for them to fix their database, and therefore she must be misaddressed or left out entirely.</p>
<p>I consider this unacceptable from old companies, but Atlantic BB was founded in 2004 – there are probably articles about not making assumptions about people’s names that are older than their company.</p>
<p>Folks, it is 2016, when companies insult people and then blame their databases it is because they do not consider all their customers worthy of equal respect.</p>
<p>So let’s get a few basics out of the way:</p>
<ul>
<li>Software reflects the biases of the people who write it and buy it.</li>
<li>If your database tells someone their name is invalid your database is not neutral. Just because you don’t get the push-back that <a href="https://en.wikipedia.org/wiki/Facebook_real-name_policy_controversy">Facebook sees</a> when they mess this up does not mean what you’re doing is okay.</li>
<li>If your database assumes my household follows 1950s social norms, the company that uses it considers 1950s social norms acceptable in 2016 – and there are probably a few of those they don’t want to defend (I hope).</li>
<li>When an email, phone rep, or letter calls me by my wife’s last name or her by mine, in both cases they are assuming she has my last name not that I have hers. This is a sexist assumption that the company has chosen to allow.</li>
</ul>
<p>Of course Atlantic isn’t the only company that does this: Verizon calls me Elizabeth in email a couple times a week because she must be primary on that account (one person must lead the family plan), and Nationwide Insurance had to hack their data fields for years so my wife could appear on our car insurance card (as required by law) every time we moved because their web interface no longer allowed the needed changes. The same bad design assumptions can be insulting for other reasons such as ethnic discrimination. <a href="/2016/09/my-grandmothers-hats/">My grandmother</a> was mis-addressed by just about everyone until she died because in the 1960s the Social Security Administration could not handle having an &rsquo; in her name, and no one was willing to fix it in the 50 years that followed SSA&rsquo;s uninvited edit to her (and many other people&rsquo;s) name.</p>
<p>In all these cases representatives all say something to the effect of “our computers cannot handle it.” And that of course is simply not true. Your systems may not be setup to handle real people, but that&rsquo;s because you don&rsquo;t believe they should be.</p>
<p>Let’s check Atlantic Broadband’s beliefs about their customers based on how they address us (I’m sure there are some additional assumptions not reflected here but these are the ones they managed to encode in one line in this letter):</p>
<ul>
<li><strong>They assume they are addressing one primary account holder:</strong> I happen to know from my interactions with them that they list my first name as: “Aaron &amp; Eliza”, and my last name as “Crosman Geor”. Plenty of households have more than one, or even two, adults who expect equal treatment in their home. Our bank and mortgage company know we are both responsible adults why is this so hard for an ISP (or insurance company, or cell provider, credit card, etc)?</li>
<li><strong>They assume my first name isn’t very long:</strong> They allowed 13 characters, but 4 more is too many. I went to high school with a kid who broke their database by exceeding the 26 character limit it had (they didn’t ask the kid to change his name, the school database admin fixed the database), but Atlantic can barely handle half that.</li>
<li><strong>They assume my last name isn’t very long:</strong> Only 12 characters were used and they stopped in a strange place. I know many people with last name longer than that: frequently people who have hyphenated last names blow past 12. Also the kid with a 26 character first name – his surname was longer.</li>
<li><strong>They assume my middle name isn&rsquo;t an important part of my name:</strong> If they had a middle name field, they could squeeze a few more letters in and make this read more sensibly.  But they only consider first and last names important. Plenty of people have three names – or more – they like to have included on letters.</li>
<li><strong>They assume it is okay to mis-address me and my wife:</strong> The name listed is just plain wrong, but they believe it&rsquo;s okay to keep using this greeting. They assume this even after they have been told it’s not, and even after we’ve reduced service with them (if another ISP provided service to my house I’d probably cut it entirely although mostly for other reasons). They believe misaddressed advertisements will convince me I need a landline or cable package again.</li>
</ul>
<p>Now I’ll be fair for just a minute and note something they got right: they allow &amp; and spaces in a name so <a href="http://xkcd.com/327/">Little Bobby Tables</a> <em>might</em> be able to be a customer without causing a crisis (partially because his name is too long for them to fit a valid SQL command into the field).</p>
<p>Frequently you’ll hear customers blame themselves because their names are too long or they have done something outside the “norm”. Let’s be clear: this is the fault of the people who write and buy the software. Software development is entirely too dominated by men, as is the leadership of large companies. When a company lacks diversity in key roles you see that reflected in the systems built to support the work. Atlantic’s leadership’s priorities and views are reflected in how their customers are addressed because they did not demand the developers correct their sexist assumptions.</p>
<p>These problems are too common for us to be able to refuse to do business when it comes up. I will say that when we switched our insurance to State Farm they did not have any trouble understanding that we had different last names and their systems accommodated that by default.</p>
<p>If you do business with a company that makes these (or other similar mistakes) I think it&rsquo;s totally reasonable to remind them every time you reasonable can that it’s offensive. Explain that they company is denying you, your loved ones, and/or your friends a major marker of their identity. Remind them they are not neutral.</p>
<p>If you write data systems for a living: check the assumptions you’re building into your code. Don’t blame the technology because you used the wrong character set or trimmed the field too short: disk is cheap, UTF-8 has been standard for 15+ years, and processors are fast. If the database or report layout doesn’t work because someone’s name is too long the flaw is not the name.</p>
<p>We all <a href="/2016/07/always-make-new-mistakes/">make mistakes</a> and bad assumptions sometimes, but that does not make it okay to deny people basic respect. When we make a bad assumption, that’s a bug, and good developers are obligated to fix it. Good companies are obligated to prevent it from happening in the first place.</p>
]]></content:encoded> </item> <item>
      <title>Try doing it backwards</title>
      <link>https://spinningcode.org/2016/09/try-doing-it-backwards/</link>
      <pubDate>
        Sun, 11 Sep 2016 16:44:53 +0000
      </pubDate> <guid
        isPermaLink="false">http://spinningcode.org/?p=104</guid>  <description>I recently rediscovered how much you can learn when you try doing something you know well backwards: I drove on the left side of the road.</description> <content:encoded><![CDATA[<p>As part of my effort <a href="/2016/07/always-make-new-mistakes/">not to repeat mistakes</a> I have tried to build a habit in my professional – and personal – life to look for ways to be better at what I do. I recently rediscovered how much you can learn when you try doing something you know well backwards: I drove on the left side of the road.</p>






<figure class="figure-center">
  
  <a href="/wp-content/uploads/2016/09/AHC_7580.jpg" target="_blank" rel="noopener noreferrer">
    
    

    











<noscript>
  <img class="rcf-image" src="/wp-content/uploads/2016/09/AHC_7580.jpg" alt="This is the Holden Barina we rented while in New Zealand. " loading="lazy" />
</noscript>

<img class="rcf-image lazyload show-if-js"
     data-srcset="/wp-content/uploads/2016/09/AHC_7580_hu_98ebd446ad8db0ab.jpg 680w, /wp-content/uploads/2016/09/AHC_7580_hu_d3247df8bccc306.jpg 850w, /wp-content/uploads/2016/09/AHC_7580_hu_c5a363f5343c2bf1.jpg 1020w, /wp-content/uploads/2016/09/AHC_7580_hu_84f7e3daa2f55011.jpg 1360w, /wp-content/uploads/2016/09/AHC_7580_hu_4621c89cd2eedb92.jpg 2040w, /wp-content/uploads/2016/09/AHC_7580.jpg 4928w"
     data-src="/wp-content/uploads/2016/09/AHC_7580.jpg"
     src="data:image/jpeg;base64,/9j/2wCEAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDIBCQkJDAsMGA0NGDIhHCEyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMv/AABEIAFUAgAMBIgACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5&#43;gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4&#43;Tl5ufo6ery8/T19vf4&#43;fr/2gAMAwEAAhEDEQA/AOjkitEfY207jWTrfhKz1Cydgnlt97ctaMsJY70TK1aa88m2WNk4PWvMO486tfBcixCWGb5R6itiPw/eW6RgS71YdCK6iN7dIeo2ntU0UiShvQcLR7QDkpPC11cL5nyrIvvTbjRryG1IaAHjqtdl9pSAfMNxFVodQE8jhhtQ/wAJoA83bTr4wldhVFb72KY09vb7YZfvngtXoM9xHGZEwnlY7159eW1pdXk8pnUkfdArSmZ1aZlXthbsfNtiMjrXO3Ak&#43;0NGf&#43;A1uLE0jPHGxVs1Tu4Lizmzcw5PG1sVtSMjLuJJYoxan5V61paN5izKUbY396s2&#43;m&#43;03QZB8qjnNbiWwitIZFbG4c0VRGgsyT3B&#43;1TGTHvSXM6wghVCxj0rCu/NjuRgYU8g&#43;tWiJGt44253ferT2pmUb28kvJMJ06VJbWPr1piQXDTFYYdqA9ae/nxT7d/z&#43;la&#43;1D2R9DHEdviqssTywcLUeXk6vwtI0suDt4XtXmHpmdJZTIe9XIvOigY7lXAqN7yeJVEifeqjeXMiQtg7ie1BkaRuPNtiXXaf71UYkYtwfM5qSzVpNPYs2W9DUaXKxuohj5/iwKAKHiOBoLUlSRuHPtXnVpn7c&#43;X4zXpur2z3Vmw83II5BrjLXRAisr8/NwelamdUbbPbwli4BdqsyXMV2GjmjVtv3eKjk8PkqW88jB4zVq0sPJRXlZTiswscRrdk1jeh40Gx&#43;1XiwGnKXrVv9O8&#43;dpi4weimsy5tTPZOqHle1dBkVJ5UkhR0UsF/SrUEqIF4yWrMtxLbRujir0UkTomW2sKfswC51Vo4XRQFesuOWS5k37/n9a073TftCtJC&#43;N1c9KLiyk8t/latAPbtE1eaVSWHmbq1BqjTZCwkYp8MFtD/AKqNR9KmCxDoAKy&#43;rGvtTNknlnPzEjb61CWk37igYVr7ov7oo3xD&#43;AUfVhe1MmT7TsUQoRnrV2yCwRsZI231O15HGMsFUepqCTWLRPvzR/8AfVP6qZ&#43;1KN5HczzFguxKyL10to/3rBXB9etUfFXiuZJkhsXHl/xMKydMuV1GbN9KMds0ey9md2GpfWC1dazcSr5VugcHj3FXLS83mKBxvZjt4/hqzbvp9srQoi5k4DYp8VrDajMUY3dc1l7SmelSyedT&#43;HUKssMMt41u5KuhxmgeHpPneP5g1WXdd3muOa2NKuHlt2&#43;XbtNOnU9oZZlgaWDpfxP3hy9x4Ynkj/1fzVRj8KXm/wCdBtr0EyPn71IZG9q6PZnie1OKTw3cxqqZ&#43;WquqeDZ7xFw3zr3xXbSyPUXmyeta&#43;yMvaGyIv8AaqXyv9qijzMUAHlUeUKPNqPzeaAMzXPD41NNqXTxH0B4rj7rwDqH/LG83/U16FnzHoo9oB5PceDdcRtu0OvpmrK&#43;G9aCootQu30NenGQd6jMn92g1pValM5eHRLia2h/diKVPXmrX9j3Z4kugv0Fb3m1EZRWXsjWni6tP&#43;Gc&#43;3h6VpQzXjsoOdpFbMIECbR6U4yA1VlPvWiVjKrVqVf4hKZetRmYiofMpCd4pmQslzUXmmoz1pJJMUAdR5tDmqufepc8UAS&#43;YmKj8xKb5gqMyDNAEvm0eZmoqTzhGaAFlkP3Ki&#43;cJ61JJJG496rZfseKAJM8d6Z8v8VReYf9qmmWgBSdlRmQ&#43;1BkyP8AappD92WgBetRyZROKTr/ABVETJ5m3krQAVFLSySSD&#43;Dikzv6rQBvfwVLn91UX8FSf8sqAI4zQ3zPRHR/y0oAMH1PTFM8pako7UAVpGIoVyKbJ1oFAEnUVVlWrfaqsvegCNG&#43;Skk6rQn3KJOqUAMlYxEbaeDkA1Fc9RUi9BQAyRjnFQnmpJPv1HQB/9k="
     data-sizes="auto"
     width="4928" alt="This is the Holden Barina we rented while in New Zealand. "
     loading="lazy" />



  </a><figcaption>This is the Holden Barina we rented while in New Zealand, a brand of car I&rsquo;d never heard of before this trip. It was a good car for the mountain driving even if the wipers and lights controls were reversed from cars at home.</figcaption></figure>

<p>By driving on the left I discovered how many basic driving habits I have that are built around driving on the right. The clearest being that the whole time I was in New Zealand I never knew if anyone was behind me, and the whole time I couldn’t figure out why. The mirrors on the car worked just fine, but it turned out I wasn’t looking at them.</p>
<p>Driving home from the airport after we returned to the US I realized that every few seconds my eyes jump to the upper right of my vision to check the mirror. In New Zealand I spent the whole time glancing at the post between the windshield and the driver’s side window (which had seemed massive to me while I was there) instead of the mirror.</p>
<p>It made me conscious of my driving habits in a way I haven’t been in years, and as a consequence, I think it’s made me a better driver. I’m thinking about little details again; I’ve been more aware of where I am on the road and what I&rsquo;m doing to keep track of the other cars around me.</p>






<figure class="figure-">
  
  <a href="/wp-content/uploads/2016/09/AHC_7568.jpg" target="_blank" rel="noopener noreferrer">
    
    

    











<noscript>
  <img class="rcf-image" src="/wp-content/uploads/2016/09/AHC_7568.jpg" alt="My wife drove this section so I got to take some pictures. Amazing scenery but she had to adjust quickly." loading="lazy" />
</noscript>

<img class="rcf-image lazyload show-if-js"
     data-srcset="/wp-content/uploads/2016/09/AHC_7568_hu_fc954da3a186a566.jpg 680w, /wp-content/uploads/2016/09/AHC_7568_hu_e18172db5046d8d1.jpg 850w, /wp-content/uploads/2016/09/AHC_7568_hu_a00a0b20e34569c3.jpg 1020w, /wp-content/uploads/2016/09/AHC_7568_hu_42f5387156ea50e8.jpg 1360w, /wp-content/uploads/2016/09/AHC_7568_hu_6c286091d0b88d13.jpg 2040w, /wp-content/uploads/2016/09/AHC_7568.jpg 4169w"
     data-src="/wp-content/uploads/2016/09/AHC_7568.jpg"
     src="data:image/jpeg;base64,/9j/2wCEAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDIBCQkJDAsMGA0NGDIhHCEyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMv/AABEIAFUAgAMBIgACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5&#43;gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4&#43;Tl5ufo6ery8/T19vf4&#43;fr/2gAMAwEAAhEDEQA/AMD&#43;yI/7lR/2RH/cro/s1H2avojwzm/7Dj/u0f2HH/drpPs1H2agDnP7DjqKTw8ldR9mo8r2oA5L/hHkqM&#43;G1rsfJ9qPJ9qAOJ/4Ro1G/hs13Xk&#43;1RGHnpSC7ODk8Mn0qtJ4Zk9K9F&#43;y5pDaj0ph7WoeayeGZf7lV5PD0o/gNenG09qhNoPSl7KmaqtVPMn0KUfwGqt1pklsm9xivUjaRdxXA&#43;LL6N7z7NF91euK5qvs6dM1o1atSpY9n8qj7NWp9loFrWvtTlMv7NR9mrZFrR9lo9qBjfZqPstbP2Wg2tHtQMb7NR5PtWp9lo&#43;y0gMryfao/svNbBtsDNRCME4BBoAzPKqLyq2fsuaiNrWwGX5XtURirVMcYOC65&#43;tM8lD0YfnSugszB1OJ4dPmkiTc23ivE7ppLi8ldvv5Oa&#43;gr&#43;1ebT5o4uX2nFeD31ldWd9Mk0TKSx5Irhxx3YE&#43;hotct5Y9/mrsqUaxbou/eCteWC6liTYj/JUo1GaMBnY4rIXsj1SPW7ST&#43;LbT/wC1rUdZFryxdSnkcOG4&#43;tTPeNJ94H8DT9qHsj1MahZbN/nL&#43;dPW&#43;tJekqfnXmcd2vlYcH86W3mCEvvK/jR7Uz9keohUbkMDS&#43;WK8xk8QSwsojvMD0zQfF9/nCOzfhij2weyO08S6lHo&#43;kyTMcPj5R615r4e8aSR6p/pUhaN3&#43;6e1UvFXiW81NEgf&#43;HqtczawTeaG8s/lXLUxX7w6aeFPd7rxHp9vGGVwzEdq56/8VPMDHAu3P8AFXGwPP5WZRtVB3qCO4luJfkYKnvWVXHVB08LTNaVp5JDJ9rcN9aabi9j&#43;7dP&#43;dSCxIgSTfu3U17cDv8AnXP7ZnRZDotY1C3P/H0xpbjWku123NvDI3qVqvHGHkG3DdakeyYPv2j1pe2qD9kc8LyTYPmqwNSVAEmxVBp4poswqBisiWbfId56V1e1Zl7I6d9UigTeFBqMa55zhEXbXOxzQmNg5PtVm1uraNQXb5vpS9pUD2ZtNdT&#43;aN8/yegrsLfTEl8NNqEUvzHjDVwkWpWRlYuGNblj4kiECWgEhjU9Kz/e1DW1MdDo9wJvMMOR60t5BcRx7I8eY3f0rdtdXgncLtwtaUljDLHuQD5qy9nVAw/DvhqG&#43;dzJIs0yrzntUN9EtrcPBsUbGxwK6OzcaOsjQoqs/VqoSx29yZJXw0jc9ay9nUNDlNRMkkeyPOP4qs6faxxWvA5/2qvvp7u&#43;RHiMVU1C7gs1zMdgPGKLMCN726EgjTDR0SGVm&#43;Xmq9ncQ34b7I/3fvZqws4huNjOAcd6LAUd1xFcPvIRVqX7ZL/z0NUrudmkd3BI3dqY4kS3adh8nagZycc0sfAcgUbGY5yai31JHK4r0jIljtnerUdl6mqwvXHYUovnz0p&#43;0I1Ne2gVpEjQZbvxV2dxbuEjAyv3mrKtdXS2jZwuZWGPpSx6jGzZkB560CNFb6dZA4bmtmLxTeCIR8VzE&#43;oW2PkDVVGooD0NID0C31yeePZIoNVpfNWTzIZWX/ZrlrfX0iH&#43;qZqvx&#43;LYvL2Pak/jWZobn9pXkELbhnA&#43;8K4PUdVub&#43;VhPJuAPFaN34ld43jij2hvWuaYlmJPU0WAu2WpXFk&#43;YX21s2us3Op3sUEpQbuN2K5kDmrVlN9mukk252mn7MDv5tJuFtnVCjj2NVo5JYLUQXELEe61mHxMo&#43;6rr&#43;NSp4vKH5o96/7VY&#43;yH7Q4zvUgqPvTxXVTEKBzU3lLjNQjrVn&#43;GtCGRZA7Ueew7CkPeozQWSGdvQUzzGptJWYEnmN60eY3rTKK0EKSTRRRQMKM0UUAOyfWkyaKKCD//2Q=="
     data-sizes="auto"
     width="4169" alt="My wife drove this section so I got to take some pictures. Amazing scenery but she had to adjust quickly."
     loading="lazy" />



  </a><figcaption>My wife drove this section while I got to take some pictures. She got to learn to drive on the left on winding mountain roads – we don&rsquo;t recommend that approach.</figcaption></figure>

<p>A few years ago I was watching videos from the <a href="http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-046j-introduction-to-algorithms-sma-5503-fall-2005/video-lectures/">MIT Algorithms course</a> to refresh some of my basics, and because I wanted to know what had been added in the decade since I’d taken that class at <a href="http://www.hamilton.edu/">Hamilton</a>. During the <a href="http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-046j-introduction-to-algorithms-sma-5503-fall-2005/video-lectures/lecture-4-quicksort-randomized-algorithms/">review of QuickSort</a> the professor mentioned that it wasn’t originally a divide-and-conqueror process, but a loop based approach meant to work on a fixed length array (so you could use a fixed block of memory). And as I recall he suggests that the students should work out the loop based version.</p>
<p>So riding on the train home from work I pieced it together, and found that it’s an elegant process. It’s not something I ever expect to have cause to implement, but it did help me improve my thinking about when to use recursive functions vs when to use a loop, and helped me think about when to use recursion, loops, and other tools for processing everything in a list.</p>
<p>There was a session by <a href="http://johnkary.net/">John Kary</a> at DrupalCon this year on <a href="https://events.drupal.org/neworleans2016/sessions/rethinking-loops">rethinking loops</a> that pushed me again to revise some of how I made those decisions. Again his talk took the reverse view of much of my previous thinking and was therefore very much worth my time.</p>
<p>If you’re feeling like you are in a good groove on something, try doing it backwards and see what you discover.</p>
]]></content:encoded> </item> </channel>
</rss>
