<feed xmlns="http://www.w3.org/2005/Atom"><title>Victoria Drake on victoria.dev</title><link href="https://victoria.dev/feed.xml" rel="self"/><link href="https://victoria.dev/"/><updated>2025-07-03T04:04:18-05:00</updated><id>https://victoria.dev/</id><author><name>Victoria Drake</name><email>hello@victoria.dev</email></author><generator>Hugo -- gohugo.io</generator><entry><title type="html">Why the Best Engineers Will Thrive Alongside AI</title><link href="https://victoria.dev/posts/why-the-best-engineers-will-thrive-alongside-ai/"/><id>https://victoria.dev/posts/why-the-best-engineers-will-thrive-alongside-ai/</id><author><name>Victoria Drake</name></author><published>2025-07-03T04:04:18-05:00</published><updated>2025-07-03T04:04:18-05:00</updated><content type="html"><![CDATA[<p>Every time I see another &ldquo;AI will replace programmers&rdquo; headline, I think about the best engineers I&rsquo;ve worked with. They&rsquo;re not the ones who write the most code or know the most algorithms. They&rsquo;re the ones who see problems clearly, design elegant solutions, and build systems that last. AI won&rsquo;t replace these people. It will make them unstoppable.</p>
<p>The engineers who thrive in an AI-augmented world won&rsquo;t be fighting against the technology or ignoring it. They&rsquo;ll understand how to amplify their strengths through intelligent collaboration with AI systems. Instead of asking &ldquo;Will AI take my job?&rdquo; they&rsquo;re asking &ldquo;How can AI make me 10x more effective at the work that matters most?&rdquo;</p>
<p>What&rsquo;s fascinating is that the skills that make you great at working with AI are remarkably similar to the skills that make you great at working with other engineers. Clear communication, structured thinking, and productive division of labor are fundamentals that remain constant whether you&rsquo;re pair programming with a colleague or collaborating with an AI model.</p>
<p>Here&rsquo;s what that collaboration looks like in practice, and how to position yourself to lead in an AI-first world.</p>
<h2 id="ai-amplifies-systems-thinking-through-better-collaboration">AI Amplifies Systems Thinking Through Better Collaboration</h2>
<p>The biggest opportunity comes from using AI to think through complex systems more thoroughly. AI excels at analyzing patterns, suggesting edge cases, and helping you reason through architectural decisions. Engineers who learn to collaborate effectively with AI on design and planning create better systems than either could build alone.</p>
<p>This mirrors how the best engineering teams work together. When you&rsquo;re designing a system with a colleague, you externalize your thinking, challenge each other&rsquo;s assumptions, and explore alternatives. Working with AI requires the same discipline. You articulate problems clearly, make your constraints explicit, and iterate on solutions collaboratively.</p>
<p>The practical skill here involves having productive conversations with AI about system design in the same way you would with a colleague.</p>
<blockquote>
<p>Start by clearly defining the problem space: What are the constraints? What are the non-obvious requirements? What could go wrong? AI can help you explore these questions more comprehensively before you commit to solutions.</p>
</blockquote>
<p>This resembles how senior engineers mentor junior team members—by asking good questions and helping them think through problems systematically. The difference is that AI can process vast amounts of information quickly and suggest patterns you might not have considered.</p>
<p>Start practicing this now by using AI to review your design documents, challenge your assumptions, and suggest alternatives. The goal is ensuring you consider angles you might have missed, just like getting a thorough code review from a thoughtful colleague.</p>
<h2 id="human-skills-become-your-competitive-advantage">Human Skills Become Your Competitive Advantage</h2>
<p>As AI handles more routine implementation work, the uniquely human aspects of engineering become increasingly important. Understanding and communicating business context, navigating organizational complexity, and making judgment calls under uncertainty—these skills differentiate great engineers from good ones.</p>
<p>Product intuition becomes especially critical. AI can generate code, but it can&rsquo;t determine whether you&rsquo;re building the right thing for solving your customers&rsquo; problems. Engineers who understand user needs, translate business requirements into technical solutions, and make trade-offs based on strategic priorities remain indispensable.</p>
<blockquote>
<p>These are the same skills that make you valuable on any engineering team. The ability to see the bigger picture, understand stakeholder needs, and make technical decisions that serve business objectives has always been what separates senior engineers from code writers.</p>
</blockquote>
<p>The ability to work across disciplines becomes more valuable as well. The best AI implementations often require understanding domain expertise, user experience implications, and business impact. Engineers who can bridge these contexts design better AI integrations, just as they design better systems when working with product managers, designers, and other stakeholders.</p>
<p>Communication skills get amplified too. Evaluating options, explaining trade-offs, and building consensus around technical decisions becomes crucial when AI can generate multiple potential solutions quickly. You&rsquo;re curating and contextualizing solutions rather than just implementing them—much like how lead engineers guide technical discussions and help teams make good decisions collectively.</p>
<h2 id="building-ai-native-systems-from-the-ground-up">Building AI-Native Systems From the Ground Up</h2>
<p>The most significant opportunities lie in designing systems built around AI capabilities from the beginning, rather than retrofitting AI into existing architectures. This requires thinking differently about how software systems work and collaborating effectively during the design process.</p>
<p>AI-native systems often need different patterns for data flow, error handling, and user interaction. They might handle probabilistic outcomes rather than deterministic ones, incorporate continuous learning loops, and provide transparency into decision-making processes. Engineers who understand these patterns early will have a significant advantage.</p>
<p>This resembles the transition any engineering team makes when adopting new paradigms. The teams that succeed are those that collaborate well during the learning process, share knowledge effectively, and iterate toward better patterns together.</p>
<p>Working with AI also means getting comfortable with a different development workflow. Instead of writing every function from scratch, you might orchestrate AI services, design feedback loops for model improvement, and build systems that get smarter about your application over time. The engineering challenge shifts from pure implementation toward integration and optimization.</p>
<p>Starting small with AI integrations in your current projects is a practical approach for seeing how AI systems can help. Add intelligent features to existing applications. Experiment with AI APIs and services. Build systems that can incorporate AI capabilities without requiring complete rewrites. Each project teaches you more about AI-native patterns, similar to how you&rsquo;d gradually adopt any new technology stack.</p>
<h2 id="developing-ai-collaboration-skills">Developing AI Collaboration Skills</h2>
<p>Learning to work effectively with AI as a thinking partner goes beyond using AI tools. You&rsquo;re developing a collaborative workflow where AI augments your problem-solving process rather than just automating tasks.</p>
<p>This means getting good at prompt engineering, but more importantly, learning to structure problems <a href="/posts/i-spent-78-learning-why-bash-still-matters-in-the-ai-age/">and code</a> in ways that AI can help with effectively. Some problems benefit from AI&rsquo;s pattern recognition capabilities. Others need AI&rsquo;s ability to generate and evaluate multiple approaches quickly. Understanding when and how to use these capabilities makes you more effective.</p>
<blockquote>
<p>Good engineers know when to ask colleagues for help, how to frame problems clearly, and which team members bring the right expertise to different challenges. Working with AI requires similar social and communication skills.</p>
</blockquote>
<p>It&rsquo;s also critical to develop good judgment about AI outputs. AI can generate impressive solutions that miss important constraints or edge cases. Engineers who can quickly evaluate AI suggestions, identify potential issues, and iterate toward better solutions will consistently outperform those who either avoid AI entirely or accept its outputs uncritically.</p>
<p>This mirrors how you&rsquo;d work with any collaborator—trusting their expertise while applying your own judgment, asking clarifying questions, and building on their contributions with your own insights and context.</p>
<h2 id="positioning-for-long-term-success">Positioning for Long-Term Success</h2>
<p>Engineers who thrive long-term will view AI as a force multiplier for their existing strengths rather than a replacement for their role. If you&rsquo;re great at system design, AI can help you explore more architectural options. If you excel at debugging, AI can help you identify patterns across larger codebases. If you&rsquo;re skilled at optimization, AI can help you analyze performance bottlenecks more comprehensively.</p>
<p>The strategic approach involves doubling down on your strengths while developing AI collaboration skills that amplify them. You don&rsquo;t need to become an AI researcher unless that&rsquo;s your passion. Instead, become expert at applying AI to the problems you already enjoy solving.</p>
<p>This also means staying close to the business impact of your work. Engineers who understand how their technical decisions affect user experience, business metrics, and organizational goals will always be valuable, regardless of how AI capabilities evolve. The technology might change, but the need for good judgment about what to build and how to build it remains constant.</p>
<h2 id="the-compound-advantage-of-early-adoption">The Compound Advantage of Early Adoption</h2>
<p>Engineers who start developing AI collaboration skills now will have years of experience when these capabilities become standard across the industry. This includes technical knowledge and intuition for when AI helps and when it doesn&rsquo;t, understanding failure modes, and building robust workflows around AI capabilities.</p>
<p>Start with AI tools that augment your current workflow—code completion, documentation generation, test writing. Gradually expand to more complex collaborations like architectural design, system optimization, and problem analysis. Each interaction teaches you more about effective AI collaboration.</p>
<p>Just like learning to work well with any new team member, the key is consistent practice and honest feedback. Try different approaches, see what works, and gradually build more sophisticated collaborative patterns.</p>
<p>The goal is becoming highly effective at leveraging AI rather than becoming dependent on it. Engineers with this skill set will consistently deliver better results faster than those working without AI augmentation. As AI capabilities improve, this advantage compounds.</p>
<blockquote>
<p>The future belongs to engineers who see AI as an opportunity to tackle harder problems, build better systems, and have greater impact. Instead of competing with AI, they&rsquo;re collaborating with it to push the boundaries of what&rsquo;s possible in software engineering.</p>
</blockquote>
<p>The best engineers have always been force multipliers—they make everyone around them more effective. AI gives these engineers a new kind of leverage. Instead of just amplifying the capabilities of their teams, they can amplify their own problem-solving abilities and tackle challenges that were previously beyond reach.</p>
<p>The practices that make you great at working with AI—clear communication, structured thinking, productive collaboration, and sound judgment—are the same practices that make you great at working with people. Master these fundamentals, and you&rsquo;ll thrive regardless of how the technology landscape evolves.</p>
]]></content></entry><entry><title type="html">From Problem Solver to Problem Solver Creator</title><link href="https://victoria.dev/posts/from-problem-solver-to-problem-solver-creator/"/><id>https://victoria.dev/posts/from-problem-solver-to-problem-solver-creator/</id><author><name>Victoria Drake</name></author><published>2025-06-24T13:06:44+00:00</published><updated>2025-06-24T13:06:44+00:00</updated><content type="html"><![CDATA[<p>The question that changed everything for me was simple: “What if instead of being the person who solves problems, I became the person who creates problem solvers?” It sounds obvious in retrospect, but the shift from solving to enabling requires completely rewiring how you think about getting work done.</p>
<p>For years, my value came from being able to debug the trickiest issues, architect complex systems, and untangle the technical problems that had everyone else stuck. I was fast, thorough, and reliable. But in a leadership role, continuing to be the primary problem solver wasn’t scaling—it was becoming a bottleneck.</p>
<p>I realized that every problem I solved myself, though satisfying, was a missed opportunity to develop someone else’s problem-solving capabilities. Instead of asking “How can I fix this?” I started asking “Who could learn the most from figuring this out, and how can I set them up for success?”</p>
<p>This mindset shift transforms everything about how you approach engineering leadership. Instead of optimizing for immediate solutions, you optimize for building a team that can tackle increasingly complex challenges independently. Here’s what that looks like in practice.</p>
<h2 id="teaching-through-ownership-not-tasks">Teaching Through Ownership, Not Tasks</h2>
<p>The difference between assigning tasks and developing problem solvers is the difference between “implement this API endpoint” and “figure out how we should handle user authentication for this new feature.” One teaches someone to follow specifications; the other teaches them to think through trade-offs and business impact, research solutions, and make technical decisions.</p>
<p>Strategic delegation becomes about identifying problems that are slightly beyond someone’s current comfort zone—complex enough to require real thinking, but not so complex that they’ll get stuck without making progress. When we needed to optimize our database performance, instead of diving in myself, I paired our most eager junior backend engineer with our database expert and said, “Figure out why our queries are getting slower and what we should do about it.” (They did an excellent job and both learned new things in the process.)</p>
<blockquote>
<p>The key is providing enough context for good decision-making while resisting the urge to prescribe the solution.</p>
</blockquote>
<p>This means sharing the business constraints, the technical requirements, and the success criteria, then stepping back and letting them work through the problem-solving process. When they hit roadblocks, you guide them toward resources and approaches rather than answers.</p>
<p>What you’re really doing is teaching people to ask the right questions: What are we optimizing for? What are the constraints? What could go wrong? How will we know if it’s working? These thinking patterns transfer to every future problem they encounter.</p>
<h2 id="building-problem-solving-muscle-through-learning">Building Problem-Solving Muscle Through Learning</h2>
<p>The best problem solvers aren’t necessarily the ones who know the most—they’re the ones who are best at learning what they need to know. Creating a culture where continuous learning is expected and supported turns every project into an opportunity to develop new problem-solving capabilities.</p>
<p>This means structuring work so that people regularly encounter unfamiliar challenges with appropriate support systems. When someone expresses curiosity about machine learning, performance optimization, or distributed systems, find ways to connect that interest to real problems your team needs to solve. The developer who wants to understand ML can take point on improving your recommendation algorithm. The engineer curious about performance can lead the investigation into why your app feels sluggish.</p>
<p>Internal knowledge sharing amplifies this effect. Regular deep-dive sessions where team members present problems they’ve solved create a library of problem-solving approaches that everyone can learn from. But more importantly, the act of sharing forces people to articulate their thinking process, which helps them develop more systematic approaches to future problems.</p>
<p>The compound effect is remarkable. Teams that prioritize learning consistently punch above their weight because they’re better at recognizing patterns, adapting to new situations, and breaking down complex problems into manageable pieces.</p>
<h2 id="communication-that-enables-independent-thinking">Communication That Enables Independent Thinking</h2>
<p>The goal of communication in leadership isn’t just clarity—it’s creating the conditions where people can make good decisions without constantly checking in with you. This means providing not just what was decided, but the reasoning behind decisions, the factors that were considered, and the principles that guide similar situations.</p>
<blockquote>
<p>When you share context richly, you’re teaching people to think through problems the way you would, but with their own insights and perspectives.</p>
</blockquote>
<p>Instead of saying “use Redis for caching,” explain why caching is needed, what alternatives were considered, what trade-offs matter, and how to evaluate whether it’s working. Now when similar performance problems arise, they have a framework for thinking through solutions.</p>
<p>One-on-ones become especially valuable for developing problem-solving skills. These conversations are where you can understand how someone approaches challenges, what assumptions they’re making, and where their thinking might benefit from different perspectives. Often, the most helpful thing you can do is ask questions that help them think through problems more systematically.</p>
<p>The ultimate goal is asynchronous problem-solving—people having enough context and judgment to tackle new challenges without waiting for direction. When that happens, your team’s problem-solving capacity isn’t limited by your bandwidth.</p>
<h2 id="identifying-and-developing-natural-problem-solving-styles">Identifying and Developing Natural Problem-Solving Styles</h2>
<p>Every engineer has a natural approach to problem-solving, but not everyone has had the opportunity to develop and refine that approach. Part of creating problem solvers is recognizing these natural inclinations and providing opportunities to strengthen them.</p>
<p>Some people are naturally systematic—they break down complex problems into smaller pieces and work through them methodically. Others are more intuitive—they see patterns and connections that aren’t immediately obvious. Some are great at asking the right questions to clarify requirements. Others excel at considering edge cases and potential failures.</p>
<p>The key is matching people with problems that play to their strengths while gradually expanding their toolkit. Let the systematic thinker lead the database migration planning. Give the pattern-recognizer the tricky debugging challenge. Ask the question-asker to work with product managers on requirement gathering.</p>
<p>But also create opportunities for people to develop complementary skills. Pair the intuitive problem solver with someone more methodical. Have the detail-oriented engineer work on a project that requires big-picture thinking. These collaborations teach people new approaches while solving real problems.</p>
<p>Leadership development happens naturally when people get comfortable with their own problem-solving style and learn to facilitate problem-solving in others.</p>
<h2 id="removing-obstacles-to-problem-solving-growth">Removing Obstacles to Problem-Solving Growth</h2>
<p>The biggest barriers to developing problem solvers are often systemic rather than individual. People can’t develop good judgment if they don’t have access to the information they need to make decisions. They can’t learn from mistakes if the environment punishes experimentation. They can’t tackle complex problems if they’re constantly interrupted by urgent but low-value work.</p>
<blockquote>
<p>Your role becomes creating the conditions where problem-solving skills can develop naturally.</p>
</blockquote>
<p>This often means advocating upward for better tools, more reasonable deadlines, or clearer priorities. It means protecting your team’s focus time and ensuring they have access to the resources they need to dive deep into problems.</p>
<p>Sometimes it’s about facilitating conversations between teams so your engineers can get the context they need to make good technical decisions. Sometimes it’s about negotiating for technical debt time so people can practice the long-term thinking that prevents problems rather than just solving them reactively.</p>
<p>The most important obstacle to remove is the fear of making mistakes. Problem-solving skills develop through experimentation, and experimentation requires an environment where intelligent failures are treated as learning opportunities rather than performance problems.</p>
<h2 id="the-multiplier-effect">The Multiplier Effect</h2>
<p>What makes this approach so rewarding is that the impact compounds exponentially. A team of capable problem solvers doesn’t just solve more problems—they solve harder problems, prevent problems through better design, and create solutions that other teams can build on.</p>
<p>When you develop someone’s problem-solving abilities, you’re not just helping them with their current role. You’re giving them tools they’ll use throughout their career, whether they stay individual contributors or move into leadership themselves. The engineer who learns to think systematically about performance problems becomes someone who designs performant systems from the start.</p>
<p>The ripple effects extend beyond your immediate team. Problem solvers become mentors. They raise the bar in technical discussions. They ask better questions in design reviews. They contribute to a culture where good technical decision-making is normal rather than exceptional.</p>
<blockquote>
<p>This is the ultimate lever in engineering leadership: instead of solving problems yourself, you create the conditions where great solutions emerge naturally from your team.</p>
</blockquote>
<p>Instead of being the bottleneck, you become the catalyst that makes everything else work better.</p>
<p>The transition from solving problems to creating problem solvers is challenging because it requires patience and faith in other people’s potential. But when you see someone tackle a problem that would have stumped them six months ago, or when your team consistently delivers solutions that surprise you with their thoughtfulness, you realize you’ve built something much more valuable than any individual technical contribution: a system that continuously generates great technical work.</p>
]]></content></entry><entry><title type="html">I Spent $78 Learning Why Bash Still Matters in the AI Age</title><link href="https://victoria.dev/posts/i-spent-78-learning-why-bash-still-matters-in-the-ai-age/"/><id>https://victoria.dev/posts/i-spent-78-learning-why-bash-still-matters-in-the-ai-age/</id><author><name>Victoria Drake</name></author><published>2025-06-15T14:09:42+00:00</published><updated>2025-06-15T14:09:42+00:00</updated><content type="html"><![CDATA[<p>Here’s how a little laziness cost me $78.</p>
<p>While working on a personal project recently, I wanted Cline to process about a hundred files that were each in subdirectories of a project. I fired up Cline and picked Gemini 2.5 Pro (context window FTW) and asked it to recurse through the subdirectories, process the files, and put the results in a new file.</p>
<p>Cline got to work… slowly. I watched as the “API Request…” spinner appeared for each file read and each time it saved the results. About twenty minutes and $26 later, it finished.</p>
<p>Okay, I thought, that’s not great, but not untenable. The cost of convenience, right? I opened up the results file to take a look and.. <em>sigh</em>. Not great work. It was obvious that some files had been skipped despite my very careful instructions to process each and every one.</p>
<p>So, like a glutton for punishment, I made a list of the files Cline had skipped and asked it to try again. Tired of babysitting, I raised the “Maximum Request Auto Approval” limit to more than I thought would be needed to finish processing the files that were left, and went to take a coffee break.</p>
<p>When I came back, Cline was done. The results? Still not great. Files had still been skipped, some files that were processed were missing results, and, oh, my task bill had risen to $78.</p>
<p>Okay, <em>this</em> was untenable. Reading all this data into context was costly and slow.</p>
<p>Then the coffee started to kick in, I guess, because it dawned on me: why in the world was I using expensive API calls to do something a Bash one-liner could do?</p>
<blockquote>
<p>&ldquo;Cline, write a Bash command that will recurse through the <code>data/</code> directory and obtain the content of all the files and copy it into a single new file.&rdquo;</p>
</blockquote>
<p>Which produced:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>find data/ -type f -exec cat <span style="color:#f92672">{}</span> + &gt; all_data.txt
</span></span></code></pre></div><p>This command:</p>
<ul>
<li><code>find data/</code> - searches recursively in the <code>data</code> directory.</li>
<li><code>-type f</code> - specifies that we&rsquo;re looking for files only (not directories, links, etc.).</li>
<li><code>-exec cat {} +</code> - for all files found, execute the <code>cat</code> command. The <code>{}</code> is a placeholder for the filename, and the <code>+</code> is a crucial optimization that groups multiple filenames into a single <code>cat</code> command, avoiding the overhead of launching a new process for every single file.</li>
<li><code>&gt; all_data.txt</code> - redirects the standard output of the <code>cat</code> command (which is the concatenated content of all the files) into a new file named <code>all_data.txt</code>.</li>
</ul>
<p>Then I asked Cline to read the resulting <code>all_data.txt</code> file, process it, and output the results.</p>
<p>It took about two minutes.</p>
<p>And it cost me $0.78.</p>
<h2 id="what-just-happened">What just happened?</h2>
<p>My initial naive approach had accidentally created a perfect storm of computational inefficiency.</p>
<p>When Cline processed each file individually, it was making separate API calls for every single operation - reads, writes, the works. With about 100 files, that meant roughly 200+ API calls, each one spinning up its own network round-trip with all the latency that entails. Every time I saw that “API Request…” spinner, I was watching money float away into the ether.</p>
<p>But here’s the kicker: large language models like Gemini charge based on token consumption.</p>
<blockquote>
<p>It’s not just the file content they’re charging for; every single API call also included the entire conversation history, system prompts, and my instructions.</p>
</blockquote>
<p>With a stateless API, that context has to be re-transmitted with every single request. If my average context was around 10,000 tokens and I made 200 calls, I burned through 2 million tokens (10,000 * 200) on overhead alone, before even counting the actual data.</p>
<p>Combining all the files with bash flipped this whole equation on its head. Instead of 200 API calls, I made exactly one. Instead of bearing the network latency for every file operation, combining the files locally on my machine meant the filesystem could actually optimize that work. What had taken almost an hour of network round-trips for Gemini to access all the data was reduced to a couple hundred milliseconds of local file operations.</p>
<h2 id="the-expensive-lesson-in-algorithmic-thinking">The expensive lesson in algorithmic thinking</h2>
<p>This whole debacle reminded me why understanding the cost model of your tools matters just as much as understanding their capabilities. API pricing is designed around per-request and per-token charges, which naturally punishes fine-grained operations. It’s similar to how databases are optimized for bulk operations rather than processing individual rows - the overhead of each transaction quickly becomes the bottleneck.</p>
<p>My first approach had O(n) complexity for API calls, where n equals the number of files. The bash solution reduced that to O(1) by batching everything locally first. That’s the difference between linear scaling and constant cost, and at $78, I felt every bit of that mathematical distinction.</p>
<p>There’s also something to be said about data locality here. My original method couldn’t take advantage of any local caching or filesystem optimizations. Every operation had to go over the network to an API server, get processed, and come back. The bash approach kept everything local until the very end, letting my machine’s filesystem cache work its magic.</p>
<h2 id="the-real-cost-of-convenience">The real cost of convenience</h2>
<p>I’d fallen into the trap of thinking that because I <em>could</em> use an AI tool for everything, I <em>should</em> use it for everything. But there’s a difference between leveraging AI for tasks that require intelligence and using it as an expensive replacement for basic system utilities.</p>
<p>The irony is that I probably spent more mental energy managing and troubleshooting the AI approach than I would have just thinking through the problem for five minutes and reaching for the right tool from the start. Sometimes the most sophisticated solution is knowing when to employ a basic tool.</p>
<p>My little bit of laziness bought me a $78 lesson that boils down to this: always understand the economic model of your tools, especially when they’re priced per operation. The most elegant and cost-effective solution isn’t always the newest and most technically exciting one.</p>
]]></content></entry><entry><title type="html">Create Better Code Documentation 10x Faster with AI</title><link href="https://victoria.dev/posts/create-better-code-documentation-10x-faster-with-ai/"/><id>https://victoria.dev/posts/create-better-code-documentation-10x-faster-with-ai/</id><author><name>Victoria Drake</name></author><published>2024-08-27T13:55:47+00:00</published><updated>2024-08-27T13:55:47+00:00</updated><content type="html"><![CDATA[<p>Documentation has always been one of those “we should do this” tasks that somehow never makes it to the top of the sprint. But what if creating comprehensive, useful documentation could be as straightforward as explaining your code to a colleague?</p>
<p>Conversational AI has changed the game entirely. Instead of starting with a blank page and trying to remember every detail a new team member might need, you can have AI help you think through the process systematically. The result isn’t just better docs—it’s documentation that actually serves your team’s needs as you grow and evolve.</p>
<p>Here’s how to use AI to build documentation that scales with your team and genuinely improves how you work together.</p>
<h2 id="documentation-that-welcomes-new-team-members">Documentation That Welcomes New Team Members</h2>
<p>The best part about using AI for documentation is that it naturally thinks from an outsider’s perspective. While you and your team already understand your system’s quirks and design decisions, AI starts fresh every time—much like a new hire would.</p>
<p>Most conversational AI tools allow you to upload code files or paste code snippets. You can then use prompts that help surface the knowledge your team takes for granted:</p>
<pre tabindex="0"><code>Write documentation for a new software engineer joining our team. Assume they’re experienced but know nothing about our specific domain, architecture decisions, or business logic. Include the “why” behind non-obvious technical choices and flag anything that might seem strange or unexpected to an outside developer.
</code></pre><p>This approach reveals the implicit knowledge that experienced team members forget to document—why certain patterns exist, what alternatives were considered, and where the potential gotchas are. It transforms documentation from a chore into a useful onboarding tool that actually reduces the time senior developers spend answering questions.</p>
<p>To create comprehensive documentation you can use immediately, provide the AI with additional context such as:</p>
<ul>
<li>What the application does and who uses it</li>
<li>Key architectural decisions and their reasoning</li>
<li>Setup and deployment processes</li>
<li>Integration points with other systems</li>
<li>Common troubleshooting scenarios</li>
</ul>
<p>Your role becomes reviewing and refining rather than writing from scratch—which is often the difference between documentation that gets done and documentation that gets skipped.</p>
<h2 id="operational-documentation-that-actually-helps">Operational Documentation That Actually Helps</h2>
<p>One of the most valuable types of documentation is also the most overlooked: information organized for when things go wrong. During incidents, you need answers fast, not comprehensive explanations.</p>
<p>AI excels at creating focused, actionable documentation because you can specify exactly what situation you’re optimizing for:</p>
<pre tabindex="0"><code>Create incident response documentation for this codebase. Focus on: 1) How to quickly identify what component is failing, 2) Common failure modes and their symptoms, 3) Step-by-step debugging workflows, 4) Who to contact for different types of issues. Write this as if the person reading it is stressed, tired, and needs answers in under 5 minutes.
</code></pre><p>This type of documentation serves a completely different purpose than your standard README or API docs. It’s designed for when your most knowledgeable developers aren’t available and someone needs to resolve an issue quickly.</p>
<p>The beauty of AI-generated operational docs is that they’re naturally structured for scan-ability rather than linear reading—exactly what you need during high-pressure situations.</p>
<h2 id="capturing-institutional-knowledge">Capturing Institutional Knowledge</h2>
<p>Here’s where AI really shines: helping you identify and document the knowledge that exists only in people’s heads. This institutional knowledge is often the difference between a change that takes 30 minutes and one that takes 3 hours of debugging.</p>
<p>You can surface these knowledge gaps by asking AI to analyze your code from a risk perspective:</p>
<pre tabindex="0"><code>Analyze this code and identify areas where domain knowledge or business context would be critical for modification. What would a developer need to know about our business, users, or regulatory requirements to safely change this code? What assumptions about data, timing, or external systems are embedded here?
</code></pre><p>For inline documentation, you can focus on the business logic and integration points that aren’t obvious from the code itself:</p>
<pre tabindex="0"><code>Add inline documentation to this code file without changing any of the code. Focus on documenting business logic, data assumptions, and integration points that wouldn’t be obvious to someone unfamiliar with our domain.
</code></pre><p>This process often improves the code itself—explaining your logic to AI sometimes reveals opportunities for clearer naming, better structure, or simplified approaches.</p>
<h2 id="making-documentation-a-team-superpower">Making Documentation a Team Superpower</h2>
<p>The real opportunity here isn’t just better individual documentation—it’s democratizing the ability to create good documentation across your entire team. Developers who previously avoided writing docs because they didn’t know where to start now have a collaborative partner to help structure their thoughts.</p>
<ol>
<li><strong>Start with high-impact documentation</strong>: Focus on onboarding guides and operational runbooks first. These provide immediate value and create positive momentum around documentation practices.</li>
<li><strong>Use AI to improve existing docs</strong>: You can ask AI to review and improve documentation you already have, suggesting missing information or better organization.</li>
<li><strong>Make it iterative</strong>: Documentation doesn’t need to be perfect on the first pass. Use AI to create initial drafts that you can refine based on team feedback and real usage patterns.</li>
<li><strong>Leverage different formats</strong>: AI can help create everything from README files to inline comments to architectural decision records, adapting the style and depth based on the audience and purpose.</li>
</ol>
<h2 id="practical-tips-for-better-results">Practical Tips for Better Results</h2>
<p>When working with AI to create documentation, providing context about the intended audience and use case dramatically improves the output. Explain not just what the code does, but who will be using the documentation and in what situations.</p>
<p>For complex codebases, you might get better results by working with smaller sections and then asking AI to help you organize everything into a coherent structure. Many AI tools can also provide downloadable files if you specify that in your prompt, which saves time on longer documents.</p>
<p>The goal isn’t to replace human judgment in documentation—it’s to remove the barriers that prevent good documentation from getting written in the first place. AI handles the initial structure and comprehensive coverage, while you focus on accuracy, team-specific context, and ensuring the documentation actually serves your workflows.</p>
<p>Good documentation transforms how teams work together. It reduces interruptions, accelerates onboarding, and creates resilience when key team members aren’t available. With AI handling the heavy lifting of initial creation, maintaining comprehensive documentation becomes achievable rather than aspirational.</p>
<p>Your future team members (and your future self during the next production incident) will definitely appreciate the investment.</p>
]]></content></entry><entry><title type="html">Post to your static website from your iPhone</title><link href="https://victoria.dev/archive/post-to-your-static-website-from-your-iphone/"/><id>https://victoria.dev/archive/post-to-your-static-website-from-your-iphone/</id><author><name>Victoria Drake</name></author><published>2024-05-05T00:00:00+00:00</published><updated>2024-05-05T00:00:00+00:00</updated><content type="html"><![CDATA[<p>I love websites. I love static sites in particular. But I know that sometimes it&rsquo;s just not practical to write and post only from your computer. With my hands full raising a family, I do a lot more development in stops and starts from my phone these days than I thought I ever would.</p>
<p>So I brought together everything that&rsquo;s great about Hugo plus everything that&rsquo;s great about sharing your 3AM thoughts with the world from your phone, thanks to Collected Notes. I put it in a new Hugo site template with a fancy new theme I call Quint.</p>
<p>You can deploy the Quint site template with one button (this button):</p>
<p><a href="https://app.netlify.com/start/deploy?repository=https://github.com/victoriadrake/quint-demo"><img src="https://www.netlify.com/img/deploy/button.svg" alt="Deploy to Netlify"></a></p>
<p>The Quint template can use the Collected Notes app as a CMS and also saves your posts to the site repository, for <a href="https://victoria.dev/posts/digital-resilience-redundancy-for-websites-and-communications/">redundancy</a>. It fetches new posts each time you build, and if you&rsquo;re deploying via Netlify or GitHub Actions, you can use a webhook to deploy the site whenever you make a new post with Collected Notes.</p>
<p>To set up your own site:</p>
<ol>
<li>Deploy the Quint template to Netlify with the button above, or clone the repo if you plan to use another deployment solution.</li>
<li>Sign up for <a href="https://collectednotes.com/">Collected Notes</a> if you haven&rsquo;t already (there&rsquo;s a free plan) and download the Collected Notes app on your iPhone.</li>
<li>Update the <code>utils/fetch-posts.js</code> file to use your Collected Notes site name.</li>
<li>Allow the GitHub Action to push changes back to your repository to save your posts. Under Settings &gt; Actions &gt; General &gt; Workflow permissions, choose Read and write permissions.</li>
</ol>
<p>Netlify will trigger a new build each time you push to your site repo, or, if you have a Collected Notes Premium subscription, you can set a <a href="https://docs.netlify.com/configure-builds/build-hooks/">Netlify Build Hook</a> URL in your Collected Notes site settings to automatically redeploy the site when you make a post or update an existing post.</p>
<p>I hope you found this post helpful! If you have any suggestions or improvements to add, feel free to <a href="https://github.com/victoriadrake/quint-demo">contribute to the repository</a>.</p>
]]></content></entry><entry><title type="html">How to send long text input to ChatGPT using the OpenAI API</title><link href="https://victoria.dev/archive/how-to-send-long-text-input-to-chatgpt-using-the-openai-api/"/><id>https://victoria.dev/archive/how-to-send-long-text-input-to-chatgpt-using-the-openai-api/</id><author><name>Victoria Drake</name></author><published>2023-09-26T04:46:36-05:00</published><updated>2023-09-26T04:46:36-05:00</updated><content type="html"><![CDATA[<p>In a previous post, I showed how you can apply text preprocessing techniques to shorten your input length for ChatGPT. Today in the web interface (<a href="https://chat.openai.com/">chat.openai.com</a>), ChatGPT allows you to send a message with a maximum token length of 4,096.</p>
<p>There are bound to be situations in which this isn&rsquo;t enough, such as when you want to read in a large amount of text from a file. Using the OpenAI API allows you to send many more tokens in a messages array, with the maximum number depending on your chosen model. This lets you provide large amounts of text to ChatGPT using chunking. Here&rsquo;s how.</p>
<h2 id="chunking-your-input">Chunking your input</h2>
<p>The <code>gpt-4</code> model currently has a maximum content length token limit of 8,192 tokens. (<a href="https://platform.openai.com/docs/models">Here are the docs containing current limits for all the models</a>.) Remember that you can first apply text preprocessing techniques to reduce your input size &ndash; in my <a href="/posts/optimizing-text-for-chatgpt-nlp-and-text-pre-processing-techniques/">previous post</a> I achieved a 28% size reduction without losing meaning with just a little tokenization and pruning.</p>
<p>When this isn&rsquo;t enough to fit your message within the maximum message token limit, you can take a general programmatic approach that sends your input in message chunks. The goal is to divide your text into sections that each fit within the model&rsquo;s token limit. The general idea is to:</p>
<ol>
<li><strong>Tokenize and split text into chunks</strong> based on the model&rsquo;s token limit. It&rsquo;s better to keep message chunks slightly below the token limit since the token limit is shared between your message and ChatGPT&rsquo;s response.</li>
<li><strong>Maintain context</strong> between chunks, e.g. avoid splitting a sentence in the middle.</li>
</ol>
<p>Each chunk is sent as a separate message in the conversation thread.</p>
<h2 id="handling-responses">Handling responses</h2>
<p>You send your chunks to ChatGPT using the OpenAI library&rsquo;s <code>ChatCompletion</code>. ChatGPT returns individual responses for each message, so you may want to process these by:</p>
<ol>
<li><strong>Concatenating responses</strong> in the order you sent them to get a coherent answer.</li>
<li><strong>Manage conversation flow</strong> by keeping track of which response refers to which chunk.</li>
<li><strong>Formatting the response</strong> to suit your desired output, e.g. replacing <code>\n</code> with line breaks.</li>
</ol>
<h2 id="putting-it-all-together">Putting it all together</h2>
<p>Using the OpenAI API, you can send multiple messages to ChatGPT and ask it to wait for you to provide all of the data before answering your prompt. Being a language model, you can provide these instructions to ChatGPT in plain language. Here&rsquo;s a suggested script:</p>
<blockquote>
<p>Prompt: Summarize the following text for me</p>
<p>To provide the context for the above prompt, I will send you text in parts. When I am finished, I will tell you &ldquo;ALL PARTS SENT&rdquo;. Do not answer until you have received all the parts.</p>
</blockquote>
<p>I created <a href="https://github.com/victoriadrake/chatgptmax">a Python module, <code>chatgptmax</code></a>, that puts all this together. It breaks up a large amount of text by a given maximum token length and sends it in chunks to ChatGPT.</p>
<p>You can install it with <code>pip install chatgptmax</code>, but here&rsquo;s the juicy part:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py" data-lang="py"><span style="display:flex;"><span><span style="color:#f92672">import</span> os
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> openai
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> tiktoken
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Set up your OpenAI API key</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Load your API key from an environment variable or secret management service</span>
</span></span><span style="display:flex;"><span>openai<span style="color:#f92672">.</span>api_key <span style="color:#f92672">=</span> os<span style="color:#f92672">.</span>getenv(<span style="color:#e6db74">&#34;OPENAI_API_KEY&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">send</span>(
</span></span><span style="display:flex;"><span>    prompt<span style="color:#f92672">=</span><span style="color:#66d9ef">None</span>,
</span></span><span style="display:flex;"><span>    text_data<span style="color:#f92672">=</span><span style="color:#66d9ef">None</span>,
</span></span><span style="display:flex;"><span>    chat_model<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;gpt-3.5-turbo&#34;</span>,
</span></span><span style="display:flex;"><span>    model_token_limit<span style="color:#f92672">=</span><span style="color:#ae81ff">8192</span>,
</span></span><span style="display:flex;"><span>    max_tokens<span style="color:#f92672">=</span><span style="color:#ae81ff">2500</span>,
</span></span><span style="display:flex;"><span>):
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    Send the prompt at the start of the conversation and then send chunks of text_data to ChatGPT via the OpenAI API.
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    If the text_data is too long, it splits it into chunks and sends each chunk separately.
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    Args:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    - prompt (str, optional): The prompt to guide the model&#39;s response.
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    - text_data (str, optional): Additional text data to be included.
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    - max_tokens (int, optional): Maximum tokens for each API call. Default is 2500.
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    Returns:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    - list or str: A list of model&#39;s responses for each chunk or an error message.
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    &#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Check if the necessary arguments are provided</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#f92672">not</span> prompt:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;Error: Prompt is missing. Please provide a prompt.&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#f92672">not</span> text_data:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;Error: Text data is missing. Please provide some text data.&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Initialize the tokenizer</span>
</span></span><span style="display:flex;"><span>    tokenizer <span style="color:#f92672">=</span> tiktoken<span style="color:#f92672">.</span>encoding_for_model(chat_model)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Encode the text_data into token integers</span>
</span></span><span style="display:flex;"><span>    token_integers <span style="color:#f92672">=</span> tokenizer<span style="color:#f92672">.</span>encode(text_data)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Split the token integers into chunks based on max_tokens</span>
</span></span><span style="display:flex;"><span>    chunk_size <span style="color:#f92672">=</span> max_tokens <span style="color:#f92672">-</span> len(tokenizer<span style="color:#f92672">.</span>encode(prompt))
</span></span><span style="display:flex;"><span>    chunks <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>        token_integers[i : i <span style="color:#f92672">+</span> chunk_size]
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">for</span> i <span style="color:#f92672">in</span> range(<span style="color:#ae81ff">0</span>, len(token_integers), chunk_size)
</span></span><span style="display:flex;"><span>    ]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Decode token chunks back to strings</span>
</span></span><span style="display:flex;"><span>    chunks <span style="color:#f92672">=</span> [tokenizer<span style="color:#f92672">.</span>decode(chunk) <span style="color:#66d9ef">for</span> chunk <span style="color:#f92672">in</span> chunks]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    responses <span style="color:#f92672">=</span> []
</span></span><span style="display:flex;"><span>    messages <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>        {<span style="color:#e6db74">&#34;role&#34;</span>: <span style="color:#e6db74">&#34;user&#34;</span>, <span style="color:#e6db74">&#34;content&#34;</span>: prompt},
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;role&#34;</span>: <span style="color:#e6db74">&#34;user&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;content&#34;</span>: <span style="color:#e6db74">&#34;To provide the context for the above prompt, I will send you text in parts. When I am finished, I will tell you &#39;ALL PARTS SENT&#39;. Do not answer until you have received all the parts.&#34;</span>,
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>    ]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> chunk <span style="color:#f92672">in</span> chunks:
</span></span><span style="display:flex;"><span>        messages<span style="color:#f92672">.</span>append({<span style="color:#e6db74">&#34;role&#34;</span>: <span style="color:#e6db74">&#34;user&#34;</span>, <span style="color:#e6db74">&#34;content&#34;</span>: chunk})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># Check if total tokens exceed the model&#39;s limit and remove oldest chunks if necessary</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">while</span> (
</span></span><span style="display:flex;"><span>            sum(len(tokenizer<span style="color:#f92672">.</span>encode(msg[<span style="color:#e6db74">&#34;content&#34;</span>])) <span style="color:#66d9ef">for</span> msg <span style="color:#f92672">in</span> messages)
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&gt;</span> model_token_limit
</span></span><span style="display:flex;"><span>        ):
</span></span><span style="display:flex;"><span>            messages<span style="color:#f92672">.</span>pop(<span style="color:#ae81ff">1</span>)  <span style="color:#75715e"># Remove the oldest chunk</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        response <span style="color:#f92672">=</span> openai<span style="color:#f92672">.</span>ChatCompletion<span style="color:#f92672">.</span>create(model<span style="color:#f92672">=</span>chat_model, messages<span style="color:#f92672">=</span>messages)
</span></span><span style="display:flex;"><span>        chatgpt_response <span style="color:#f92672">=</span> response<span style="color:#f92672">.</span>choices[<span style="color:#ae81ff">0</span>]<span style="color:#f92672">.</span>message[<span style="color:#e6db74">&#34;content&#34;</span>]<span style="color:#f92672">.</span>strip()
</span></span><span style="display:flex;"><span>        responses<span style="color:#f92672">.</span>append(chatgpt_response)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Add the final &#34;ALL PARTS SENT&#34; message</span>
</span></span><span style="display:flex;"><span>    messages<span style="color:#f92672">.</span>append({<span style="color:#e6db74">&#34;role&#34;</span>: <span style="color:#e6db74">&#34;user&#34;</span>, <span style="color:#e6db74">&#34;content&#34;</span>: <span style="color:#e6db74">&#34;ALL PARTS SENT&#34;</span>})
</span></span><span style="display:flex;"><span>    response <span style="color:#f92672">=</span> openai<span style="color:#f92672">.</span>ChatCompletion<span style="color:#f92672">.</span>create(model<span style="color:#f92672">=</span>chat_model, messages<span style="color:#f92672">=</span>messages)
</span></span><span style="display:flex;"><span>    final_response <span style="color:#f92672">=</span> response<span style="color:#f92672">.</span>choices[<span style="color:#ae81ff">0</span>]<span style="color:#f92672">.</span>message[<span style="color:#e6db74">&#34;content&#34;</span>]<span style="color:#f92672">.</span>strip()
</span></span><span style="display:flex;"><span>    responses<span style="color:#f92672">.</span>append(final_response)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> responses
</span></span></code></pre></div><p>Here&rsquo;s an example of how you can use this module with text data read from a file. (<code>chatgptmax</code> also provides a <a href="https://github.com/victoriadrake/chatgptmax/blob/4431af468435cd51d07779c6d721c8e0016d6bd6/chatgptmax.py#L68">convenience method</a> for getting text from a 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;"><code class="language-py" data-lang="py"><span style="display:flex;"><span><span style="color:#75715e"># First, import the necessary modules and the function</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> os
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> chatgptmax <span style="color:#f92672">import</span> send
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Define a function to read the content of a file</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">read_file_content</span>(file_path):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">with</span> open(file_path, <span style="color:#e6db74">&#39;r&#39;</span>, encoding<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;utf-8&#39;</span>) <span style="color:#66d9ef">as</span> file:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> file<span style="color:#f92672">.</span>read()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Use the function</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> __name__ <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;__main__&#34;</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Specify the path to your file</span>
</span></span><span style="display:flex;"><span>    file_path <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;path_to_your_file.txt&#34;</span>
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Read the content of the file</span>
</span></span><span style="display:flex;"><span>    file_content <span style="color:#f92672">=</span> read_file_content(file_path)
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Define your prompt</span>
</span></span><span style="display:flex;"><span>    prompt_text <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;Summarize the following text for me:&#34;</span>
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Send the file content to ChatGPT</span>
</span></span><span style="display:flex;"><span>    responses <span style="color:#f92672">=</span> send(prompt<span style="color:#f92672">=</span>prompt_text, text_data<span style="color:#f92672">=</span>file_content)
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Print the responses</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> response <span style="color:#f92672">in</span> responses:
</span></span><span style="display:flex;"><span>        print(response)
</span></span></code></pre></div><h3 id="error-handling">Error handling</h3>
<p>While the module is designed to handle most standard use cases, there are potential pitfalls to be aware of:</p>
<ul>
<li><strong>Incomplete sentences</strong>: If a chunk ends in the middle of a sentence, it might alter the meaning or context. To mitigate this, consider ensuring that chunks end at full stops or natural breaks in the text. You could do this by separating the text-chunking task into a separate function that:
<ol>
<li>Splits the text into sentences.</li>
<li>Iterates over the sentences and adds them to a chunk until the chunk reaches the maximum size.</li>
<li>Starts a new chunk when the current chunk reaches the maximum size or when adding another sentence would exceed the maximum size.</li>
</ol>
</li>
<li><strong>API connectivity issues</strong>: There&rsquo;s always a possibility of timeouts or connectivity problems during API calls. If this is a significant issue for your application, you can include retry logic in your code. If an API call fails, the script could wait for a few seconds and then try again, ensuring that all chunks are processed.</li>
<li><strong>Rate limits</strong>: Be mindful of <a href="https://platform.openai.com/docs/guides/rate-limits/overview">OpenAI API&rsquo;s rate limits</a>. If you&rsquo;re sending many chunks in rapid succession, you might hit these limits. Introducing a slight delay between calls or spreading out requests can help avoid this.</li>
</ul>
<h3 id="optimization">Optimization</h3>
<p>As with any process, there&rsquo;s always room for improvement. Here are a couple of ways you might optimize the module&rsquo;s chunking and sending process further:</p>
<ul>
<li><strong>Parallelizing API calls</strong>: If <a href="https://platform.openai.com/docs/guides/rate-limits/overview">OpenAI API&rsquo;s rate limits</a> and your infrastructure allow, you could send multiple chunks simultaneously. This parallel processing can speed up the overall time it takes to get responses for all chunks. Unless you have access to OpenAI&rsquo;s <code>32k</code> models or need to use small chunk sizes, however, parallelism gains are likely to be minimal.</li>
<li><strong>Caching mechanisms</strong>: If you find yourself sending the same or similar chunks frequently, consider implementing a caching system. By storing ChatGPT&rsquo;s responses for specific chunks, you can retrieve them instantly from the cache the next time, saving both time and API calls.</li>
</ul>
<h2 id="now-what">Now what</h2>
<p>If you found your way here via search, you probably already have a use case in mind. Here are some other (startup) ideas:</p>
<ul>
<li><strong>You&rsquo;re a researcher</strong> who wants to save time by getting short summaries of many lengthy articles.</li>
<li><strong>You&rsquo;re a legal professional</strong> who wants to analyze long contracts by extracting key points or clauses.</li>
<li><strong>You&rsquo;re a financial analyst</strong> who wants to pull a quick overview of trends from a long report.</li>
<li><strong>You&rsquo;re a writer</strong> who wants feedback on a new article or chapter&hellip; without having to actually show it to anyone yet.</li>
</ul>
<p>Do you have a use case I didn&rsquo;t list? <a href="/contact">Let me know about it!</a> In the meantime, have fun sending lots of text to ChatGPT.</p>
]]></content></entry><entry><title type="html">Optimizing text for ChatGPT: NLP and text pre-processing techniques</title><link href="https://victoria.dev/archive/optimizing-text-for-chatgpt-nlp-and-text-pre-processing-techniques/"/><id>https://victoria.dev/archive/optimizing-text-for-chatgpt-nlp-and-text-pre-processing-techniques/</id><author><name>Victoria Drake</name></author><published>2023-09-19T04:46:36-05:00</published><updated>2023-09-19T04:46:36-05:00</updated><content type="html"><![CDATA[<p>In order for chatbots and voice assistants to be helpful, they need to be able to take in and understand our instructions in plain language using Natural Language Processing (NLP). ChatGPT relies on a blend of advanced algorithms and text preprocessing methods to make sense of our words. But just throwing a wall of text at it can be inefficient &ndash; you might be dumping in a lot of noise with that signal and hitting the text input limit.</p>
<p>Text preprocessing can help shorten and refine your input, ensuring that ChatGPT can grasp the essence without getting overwhelmed. In this article, we&rsquo;ll explore these techniques, understand their importance, and see how they make your interactions with tools like ChatGPT more reliable and productive.</p>
<h2 id="text-preprocessing">Text preprocessing</h2>
<p>Text preprocessing prepares raw text data for analysis by NLP models. Generally, it distills everyday text (like full sentences) to make it more manageable or concise and meaningful. Techniques include:</p>
<ul>
<li><strong>Tokenization:</strong> splitting up text by sentences or paragraphs. For example, you could break down a lengthy legal document into individual clauses or sentences.</li>
<li><strong>Extractive summarization:</strong> selecting key sentences from the text and discarding the rest. Instead of reading an entire 10-page document, extractive summarization could pinpoint the most crucial sentences and give you a concise overview without delving into the details.</li>
<li><strong>Abstractive summarization:</strong> generating a concise representation of the text content, for example, turning a 10-page document into a brief paragraph that captures the document&rsquo;s essence in new wording.</li>
<li><strong>Pruning:</strong> removing redundant or less relevant parts. For example, in a verbose email thread, pruning can help remove all the greetings, sign-offs, and other repetitive elements, leaving only the core content for analysis.</li>
</ul>
<p>While all these techniques can help reduce the size of raw text data, some of these techniques are easier to apply to general use cases than others. Let&rsquo;s examine how text preprocessing can help us send a large amount of text to ChatGPT.</p>
<h2 id="tokenization-and-chatgpt-input-limits">Tokenization and ChatGPT input limits</h2>
<p>In the realm of Natural Language Processing (NLP), a token is the basic unit of text that a system reads. At its simplest, you can think of a token as a word, but depending on the language and the specific tokenization method used, a token can represent a word, part of a word, or even multiple words.</p>
<p>While in English we often equate tokens with words, in NLP, the concept is broader. A token can be as short as a single character or as long as a word. For example, with word tokenization, the sentence &ldquo;Unicode characters such as emojis are not indivisible. ✂️&rdquo; can be broken down into tokens like this: [&ldquo;Unicode&rdquo;, &ldquo;characters&rdquo;, &ldquo;such&rdquo;, &ldquo;as&rdquo;, &ldquo;emojis&rdquo;, &ldquo;are&rdquo;, &ldquo;not&rdquo;, &ldquo;indivisible&rdquo;, &ldquo;.&rdquo;, &ldquo;✂️&rdquo;]</p>
<p>In another form called Byte-Pair Encoding (BPE), the same sentence is tokenized as: [&ldquo;Un&rdquo;, &ldquo;ic&rdquo;, &ldquo;ode&rdquo;, &quot; characters&quot;, &quot; such&quot;, &quot; as&quot;, &quot; em, &ldquo;oj&rdquo;, &ldquo;is&rdquo;, &quot; are&quot;, &quot; not&quot;, &quot; ind&quot;, &ldquo;iv&rdquo;, &ldquo;isible&rdquo;, &ldquo;.&rdquo;, &quot; �&quot;, &ldquo;�️&rdquo;]. The emoji itself is split into tokens containing its underlying bytes.</p>
<p>Depending on the ChatGPT model chosen, your text input size is restricted by tokens. <a href="https://platform.openai.com/docs/models">Here are the docs containing current limits</a>. BPE is used by ChatGPT to determine token count, and we&rsquo;ll discuss it more thoroughly later. First, we can programmatically apply some preprocessing techniques to reduce our text input size and use fewer tokens.</p>
<h2 id="a-general-programmatic-approach">A general programmatic approach</h2>
<p>For a general approach that can be applied programmatically, pruning is a suitable preprocessing technique. One form is <strong>stop word removal,</strong> or removing common words that might not add significant meaning in certain contexts. For example, consider the sentence:</p>
<p>&ldquo;I always enjoy having pizza with my friends on weekends.&rdquo;</p>
<p>Stop words are often words that don&rsquo;t carry significant meaning on their own in a given context. In this sentence, words like &ldquo;I&rdquo;, &ldquo;always&rdquo;, &ldquo;enjoy&rdquo;, &ldquo;having&rdquo;, &ldquo;with&rdquo;, &ldquo;my&rdquo;, &ldquo;on&rdquo; are considered stop words.</p>
<p>After removing the stop words, the sentence becomes:</p>
<p>&ldquo;pizza friends weekends.&rdquo;</p>
<p>Now, the sentence is distilled to its key components, highlighting the main subject (pizza) and the associated context (friends and weekends). If you find yourself wishing you could convince people to do this in real life (<em>cough</em>meetings<em>cough</em>)&hellip; you aren&rsquo;t alone.</p>
<p>Stop word removal is straightforward to apply programmatically: given a list of stop words, examine some text input to see if it contains any of the stop words on your list. If it does, remove them, then return the altered text.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py" data-lang="py"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">clean_stopwords</span>(text: str) <span style="color:#f92672">-&gt;</span> str:
</span></span><span style="display:flex;"><span>    stopwords <span style="color:#f92672">=</span> [<span style="color:#e6db74">&#34;a&#34;</span>, <span style="color:#e6db74">&#34;an&#34;</span>, <span style="color:#e6db74">&#34;and&#34;</span>, <span style="color:#e6db74">&#34;at&#34;</span>, <span style="color:#e6db74">&#34;but&#34;</span>, <span style="color:#e6db74">&#34;how&#34;</span>, <span style="color:#e6db74">&#34;in&#34;</span>, <span style="color:#e6db74">&#34;is&#34;</span>, <span style="color:#e6db74">&#34;on&#34;</span>, <span style="color:#e6db74">&#34;or&#34;</span>, <span style="color:#e6db74">&#34;the&#34;</span>, <span style="color:#e6db74">&#34;to&#34;</span>, <span style="color:#e6db74">&#34;what&#34;</span>, <span style="color:#e6db74">&#34;will&#34;</span>]
</span></span><span style="display:flex;"><span>    tokens <span style="color:#f92672">=</span> text<span style="color:#f92672">.</span>split()
</span></span><span style="display:flex;"><span>    clean_tokens <span style="color:#f92672">=</span> [t <span style="color:#66d9ef">for</span> t <span style="color:#f92672">in</span> tokens <span style="color:#66d9ef">if</span> <span style="color:#f92672">not</span> t <span style="color:#f92672">in</span> stopwords]
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34; &#34;</span><span style="color:#f92672">.</span>join(clean_tokens)
</span></span></code></pre></div><p>To see how effective stop word removal can be, I took the entire text of my <a href="https://techleaderdocs.com">Tech Leader Docs newsletter</a> (17,230 words consisting of 104,892 characters) and processed it using the above function. How effective was it? The resulting text contained 89,337 characters, which is about a 15% reduction in size.</p>
<p>Other pruning techniques can also be applied programmatically. Removing punctuation, numbers, HTML tags, URLs and email addresses, or non-alphabetical characters are all valid pruning techniques that can be straightforward to apply. Here is a function that does just that:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py" data-lang="py"><span style="display:flex;"><span><span style="color:#f92672">import</span> re
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">clean_text</span>(text):
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Remove URLs</span>
</span></span><span style="display:flex;"><span>    text <span style="color:#f92672">=</span> re<span style="color:#f92672">.</span>sub(<span style="color:#e6db74">r</span><span style="color:#e6db74">&#39;http\S+&#39;</span>, <span style="color:#e6db74">&#39;&#39;</span>, text)
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Remove email addresses</span>
</span></span><span style="display:flex;"><span>    text <span style="color:#f92672">=</span> re<span style="color:#f92672">.</span>sub(<span style="color:#e6db74">r</span><span style="color:#e6db74">&#39;\S+@\S+&#39;</span>, <span style="color:#e6db74">&#39;&#39;</span>, text)
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Remove everything that&#39;s not a letter (a-z, A-Z)</span>
</span></span><span style="display:flex;"><span>    text <span style="color:#f92672">=</span> re<span style="color:#f92672">.</span>sub(<span style="color:#e6db74">r</span><span style="color:#e6db74">&#39;[^a-zA-Z\s]&#39;</span>, <span style="color:#e6db74">&#39;&#39;</span>, text)
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Remove whitespace, tabs, and new lines</span>
</span></span><span style="display:flex;"><span>    text <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;&#39;</span><span style="color:#f92672">.</span>join(text<span style="color:#f92672">.</span>split())
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> text
</span></span></code></pre></div><p>What measure of length reduction might we be able to get from this additional processing? Applying these techniques to the remaining characters of Tech Leader Docs results in just 75,217 characters; an overall reduction of about 28% from the original text.</p>
<p>More opinionated pruning, such as removing short words or specific words or phrases, can be tailored to a specific use case. These don&rsquo;t lend themselves well to general functions, however.</p>
<p>Now that you have some text processing techniques in your toolkit, let&rsquo;s look at how a reduction in characters translates to fewer tokens used when it comes to ChatGPT. To understand this, we&rsquo;ll examine Byte-Pair Encoding.</p>
<h2 id="byte-pair-encoding-bpe">Byte-Pair Encoding (BPE)</h2>
<p>Byte-Pair Encoding (BPE) is a subword tokenization method. It was originally introduced for data compression but has since been adapted for tokenization in NLP tasks. It allows representing common words as tokens and splits more rare words into subword units. This enables a balance between character-level and word-level tokenization.</p>
<p>Let&rsquo;s make that more concrete. Imagine you have a big box of LEGO bricks, and each brick represents a single letter or character. You&rsquo;re tasked with building words using these LEGO bricks. At first, you might start by connecting individual bricks to form words. But over time, you notice that certain combinations of bricks (or characters) keep appearing together frequently, like &ldquo;th&rdquo; in &ldquo;the&rdquo; or &ldquo;ing&rdquo; in &ldquo;running.&rdquo;</p>
<p>BPE is like a smart LEGO-building buddy who suggests, &ldquo;Hey, since &rsquo;th&rsquo; and &lsquo;ing&rsquo; keep appearing together a lot, why don&rsquo;t we glue them together and treat them as a single piece?&rdquo; This way, the next time you want to build a word with &ldquo;the&rdquo; or &ldquo;running,&rdquo; you can use these glued-together pieces, making the process faster and more efficient.</p>
<p>Colloquially, the BPE algorithm looks like this:</p>
<ol>
<li>Start with single characters.</li>
<li>Observe which pairs of characters frequently appear together.</li>
<li>Merge those frequent pairs together to treat them as one unit.</li>
<li>Repeat this process until you have a mix of single characters and frequently occurring character combinations.</li>
</ol>
<p>BPE is a particularly powerful tokenization method, especially when dealing with diverse and extensive vocabularies. Here&rsquo;s why:</p>
<ul>
<li>Handling rare words: Traditional tokenization methods might stumble upon rare or out-of-vocabulary words. BPE, with its ability to break words down into frequent subword units, can represent these words without needing to have seen them before.</li>
<li>Efficiency: By representing frequent word parts as single tokens, BPE can compress text more effectively. This is especially useful for models like ChatGPT, where token limits apply.</li>
<li>Adaptability: BPE is language-agnostic. It doesn&rsquo;t rely on predefined dictionaries or vocabularies. Instead, it learns from the data, making it adaptable to various languages and contexts.</li>
</ul>
<p>In essence, BPE strikes a balance, offering the granularity of character-level tokenization and the context-awareness of word-level tokenization. This hybrid approach ensures that NLP models like ChatGPT can understand a wide range of texts while maintaining computational efficiency.</p>
<h2 id="sending-lots-of-text-to-chatgpt">Sending lots of text to ChatGPT</h2>
<p>At time of writing, a message to ChatGPT via its web interface has a maximum token length of 4,096 tokens. If we assume the prior mentioned percent reduction as an average, this means you could reduce text of up to 5,712 tokens down to the appropriate size with just text preprocessing.</p>
<p>What about when this isn&rsquo;t enough? Beyond text preprocessing, larger input can be sent in chunks using the OpenAI API. In my next post, I&rsquo;ll show you how to build a Python module that does exactly that.</p>
]]></content></entry><entry><title type="html">Mastering Git for Small Teams</title><link href="https://victoria.dev/posts/mastering-git-for-small-teams/"/><id>https://victoria.dev/posts/mastering-git-for-small-teams/</id><author><name>Victoria Drake</name></author><published>2022-02-28T06:37:48-06:00</published><updated>2022-02-28T06:37:48-06:00</updated><content type="html"><![CDATA[<p>I&rsquo;ve watched too many talented engineers spend their Friday afternoons untangling Git messes that could have been avoided with a simpler workflow. You know the scene: someone&rsquo;s trying to merge a three-week-old feature branch, there are conflicts in files that haven&rsquo;t been touched in months, and suddenly what should have been a five-minute deployment turns into a two-hour debugging session (with the whole team).</p>
<p>The solution isn&rsquo;t mastering Git&rsquo;s most obscure commands or memorizing every branching strategy ever invented. It&rsquo;s adopting a workflow that prevents the chaos in the first place. Here&rsquo;s the approach I use personally and recommend for small teams that want to ship code without the drama.</p>
<h2 id="a-protected-main-branch-no-exceptions">A Protected Main Branch (No Exceptions)</h2>
<p>First rule: no human should have direct push permissions to your <code>master</code> branch. Ever. I don&rsquo;t care if you&rsquo;re the CTO, the person who started the repository, or the only one who &ldquo;really understands the codebase.&rdquo; The moment you start making exceptions is the moment you start breaking things in production.</p>
<p>Your main branch should be your source of truth for what&rsquo;s currently deployed. When you create a release from the latest tag, that code should work. Period. If you&rsquo;re not deploying frequently and automatically, you&rsquo;re missing out on one of the biggest advantages of this approach.</p>
<h2 id="one-issue-one-branch-one-pr-keep-it-simple">One Issue, One Branch, One PR (Keep It Simple)</h2>
<p>Here&rsquo;s where most teams overcomplicate things. You&rsquo;ve got your issues tracked somewhere (and if you don&rsquo;t, we need to have a different conversation). Each issue represents a well-defined piece of work that can be merged and deployed without breaking anything. Maybe it&rsquo;s a new feature, a component update, or a bug fix. Doesn&rsquo;t matter—the process stays the same.</p>
<figure><img src="/posts/mastering-git-for-small-teams/cover.png"><figcaption>
      <h4>Author&#39;s illustration of issue branches and releases from master.</h4>
    </figcaption>
</figure>

<p>The key is keeping branches short-lived. For a small commercial team, we&rsquo;re talking days, not weeks. Open source projects with volunteer contributors might stretch this to a few weeks or months, but the principle remains: finish the work, get it reviewed, merge it, and move on.</p>
<p>Here&rsquo;s what this looks like in practice. Say you&rsquo;re working on <strong>(#28) Add user settings page</strong>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span><span style="color:#75715e"># Get all the latest work locally</span>
</span></span><span style="display:flex;"><span>git checkout master
</span></span><span style="display:flex;"><span>git pull
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Start your new branch from master</span>
</span></span><span style="display:flex;"><span>git checkout -b 28/add-settings-page
</span></span></code></pre></div><p>Work on the issue, and periodically merge <code>master</code> to stay current:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span><span style="color:#75715e"># Commit to your issue branch</span>
</span></span><span style="display:flex;"><span>git commit ...
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Get the latest work on master</span>
</span></span><span style="display:flex;"><span>git checkout master
</span></span><span style="display:flex;"><span>git pull
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Return to your issue branch and merge in master</span>
</span></span><span style="display:flex;"><span>git checkout 28/add-settings-page
</span></span><span style="display:flex;"><span>git merge master
</span></span></code></pre></div><p>I know some of you are thinking &ldquo;but what about rebasing?&rdquo; Look, I like rebasing too. A clean, linear history is beautiful. But I&rsquo;ve seen too many developers get tangled up in interactive rebasing purgatory while accidentally dropping commits or creating conflicts that didn&rsquo;t need to exist. Merging might create a slightly messier history, but it&rsquo;s predictable and reversible. When you&rsquo;re optimizing for team productivity, predictable wins over pretty.</p>
<p>When your work is ready, open a PR against <code>master</code>. Tests run automatically. Your teammates review the code and leave helpful feedback (hopefully). Maybe you deploy a preview version to staging. Once everything looks good, merge it, close the issue, and delete the branch. (Yes. Delete it. It will be okay.)</p>
<h2 id="avoiding-the-common-disasters">Avoiding the Common Disasters</h2>
<p>Here are the patterns I see that turn this simple workflow into a nightmare:</p>
<p><strong>Branching off feature branches:</strong> This is how you end up with dependency chains that make merging feel like getting the Christmas lights out of storage. Someone starts working on feature B before feature A is merged, then feature C depends on both, and suddenly you need a whiteboard and a computer science degree to figure out the merge order. Just branch from the latest <code>master</code>. Always.</p>
<p><strong>Scope creep on branches:</strong> You&rsquo;re implementing the user settings page, but then you notice the button component could use some updates, and hey, while we&rsquo;re at it, let&rsquo;s refactor this entire authentication flow. Stop. That&rsquo;s how a three-day task becomes a three-week (or three-month) PR that nobody wants to review. Stick to the issue at hand.</p>
<p><strong>Keeping dead branches around:</strong> Your branch got merged last month, but it&rsquo;s still sitting there in the repository like a ghost haunting your Git history. Delete merged branches immediately. Future you will thank present you for not having to scroll through fifty old feature branches trying to find the one you&rsquo;re actually working on.</p>
<h2 id="why-this-actually-works">Why This Actually Works</h2>
<p>This workflow works because it aligns with how small teams actually operate. You don&rsquo;t need the complexity of GitFlow when you&rsquo;ve got eight developers. You don&rsquo;t need long-lived release branches when you&rsquo;re deploying multiple times per week. You need a system that gets out of your way and lets you focus on building software.</p>
<p>The protection on <code>master</code> means your deployable code stays deployable. The one-issue-per-branch rule keeps PRs reviewable and prevents feature creep. The short-lived branches mean conflicts are small and manageable. The regular merging from <code>master</code> means you catch integration issues early when they&rsquo;re easy to fix.</p>
<p>Most importantly, <strong>this workflow is boring in the best possible way.</strong> Once your team gets the hang of it, Git becomes background infrastructure instead of a daily source of stress. Developers stop losing work to merge conflicts. Code reviews become focused discussions about functionality rather than archaeology expeditions through weeks of accumulated changes.</p>
<p>The best development workflows are the ones you don&rsquo;t have to think about. They handle the routine stuff automatically so you can focus on the interesting problems. This Git strategy does exactly that—it gets out of your way and lets you ship code with confidence.</p>
]]></content></entry><entry><title type="html">Introducing The Tech Leader Docs</title><link href="https://victoria.dev/archive/introducing-the-tech-leader-docs/"/><id>https://victoria.dev/archive/introducing-the-tech-leader-docs/</id><author><name>Victoria Drake</name></author><published>2021-12-21T06:28:06-06:00</published><updated>2021-12-21T06:28:06-06:00</updated><content type="html"><![CDATA[<p>I&rsquo;m launching a brand new paid newsletter on Substack focused on building, growing, and leading your technology teams to success. It’s a short, no-time-wasted bi-weekly newsletter that will give you immediately applicable skills and strategies you can take to work that day.</p>
<p>Here are a few things past colleagues have said about my work in software engineering leadership:</p>
<blockquote>
<p><em>&quot;&hellip;the level of organization and process we had was amazing and I personally want to duplicate as much of it as possible!&quot;</em></p>
</blockquote>
<blockquote>
<p><em>&quot;&hellip;remember how excited I was in my interview with you Victoria about how well-organized [your previous company] was and how well-thought-out your processes were? I dare say 99.99% of startups, and many larger companies aren&rsquo;t very organized and it becomes a pain point as they grow. Between the docs and the other processes you had in place, I think you could write a really good book that would be a &lsquo;Blueprint&rsquo; for forming a technical team.&quot;</em></p>
</blockquote>
<blockquote>
<p><em>&quot;@Victoria Drake you publish a book and I will buy at least a dozen copies to hand out.&quot;</em></p>
</blockquote>
<p>Instead of waiting to collect all this information in book form, the first post goes out the first week of January. Here&rsquo;s a preview of some of the skills you’ll learn in future editions:</p>
<ul>
<li>How to set up remote and asynchronous work to be super-efficient</li>
<li>How to remove the right restrictions to make your processes more productive</li>
<li>Setting up safeguards to ensure progress doesn&rsquo;t slow down when you take time off</li>
<li>Hiring for the culture you want</li>
<li>Creating the number one secret weapon that makes everyone on your team more productive</li>
</ul>
<p>These insights are for senior engineers and engineering managers, as well as anyone who wants to start establishing yourself as a leader on your team right away.</p>
<p>You can subscribe monthly, or <a href="https://techleaderdocs.substack.com/nyr">lock in an early-bird New Year Special subscription for 30% off before Jan 1.</a></p>
<p>My website Victoria.dev isn’t going anywhere, I&rsquo;ve just decided to put more effort into this new resource. The paid newsletter format strikes a good balance between the immediate delivery of information, skin-in-the-game for you to implement these ideas, and motivation for me to keep sharing how I acquired the skills and knowledge that live in my head, just as I’ve done for years on my blog.</p>
<p>I&rsquo;ve held all kinds of roles on engineering teams, from contract developer to Director of Engineering. Over the years I&rsquo;ve heard from past colleagues and peers about confusing processes, time-wasting meetings, and poor leadership in companies of all sizes. With The Tech Leader Docs, I hope to help those in positions to create positive change in their organization (including you!) to turn that feedback around.</p>
<p><a href="https://techleaderdocs.substack.com/nyr">Subscribe today and lock in 30% off.</a> You’ll start 2022 with the practical skills it takes to build a successful engineering team: smarter strategies, less toil, and happier and more productive developers.</p>
]]></content></entry><entry><title type="html">My paper to-do strategy</title><link href="https://victoria.dev/archive/my-paper-to-do-strategy/"/><id>https://victoria.dev/archive/my-paper-to-do-strategy/</id><author><name>Victoria Drake</name></author><published>2021-10-25T12:17:32+00:00</published><updated>2021-10-25T12:17:32+00:00</updated><content type="html"><![CDATA[<p>Coding up a to-do app may be the Hello, World of every framework, but when it comes to actually tracking tasks effectively (knock &rsquo;em out not stack &rsquo;em up) there&rsquo;s no app that keeps things front of mind better than an open notebook on your desk.</p>
<p>Here&rsquo;s my stupid-simple strategy for tracking and checking off my to-do list.</p>
<h2 id="one-page-at-a-time">One page at a time</h2>
<p>Plenty of methodologies recommend using sections or different pages of your book for monthly, weekly, and daily views; others advocate for creating sections for each category, such as &ldquo;Home Tasks&rdquo; and &ldquo;Work Tasks&rdquo; and other such time-wasters. All of this is unnecessary.</p>
<p>A to-do list works because it&rsquo;s in your face and hard to miss. When you write things down on different pages, they become easy to miss. Don&rsquo;t do that.</p>
<p>Use one page at a time. Write down one task under another. Don&rsquo;t sort them, prioritize them (yet), or categorize anything. Just write them down on the current page, where you&rsquo;re guaranteed to look when you lay eyes on your notebook next.</p>
<h2 id="intuitive-notation">Intuitive notation</h2>
<p>I use my notebook for two things: short notes (just a bit of information &ndash; nothing to do) and tasks (something to do). This translates to a notation system of three possible states:</p>
<ul>
<li>It&rsquo;s a note, indicated with a bullet point</li>
<li>It&rsquo;s a new task, indicated with a checkbox</li>
<li>It&rsquo;s a completed task, with the checkbox checked and the line struck out (because strike-throughs are <em>satisfying</em>)</li>
</ul>
<p><img src="a9ccelphZv.jpeg" alt="A picture of my task list"></p>
<p>I use a checkbox to distinguish tasks from notes because I&rsquo;m an old-school HTML fan, but you do you.</p>
<p>You may like to add your own embellishments to this: I sometimes denote an urgent item with an asterisk. You might like to use a color pen or highlighter (avoid the bullet journal rabbit hole &ndash; another time-waster). Just keep it simple, repeatable, and intuitive.</p>
<h2 id="when-its-time-to-turn-the-page">When it&rsquo;s time to turn the page</h2>
<p>When life gets busy, you might fill up a page pretty quickly. If one or two tasks haven&rsquo;t yet been crossed off, they&rsquo;re liable to be forgotten. You can avoid this by carrying tasks over to the next page.</p>
<p>It&rsquo;s straightforward: cross out the task on the page that&rsquo;s filled up. Turn the page and write it down there again.</p>
<p><em>That&rsquo;s silly,</em> you might say, <em>that&rsquo;s a waste of energy! By the time I write it down all over again, I could&rsquo;ve done half of it already.</em></p>
<p>&hellip;</p>
<p>I&rsquo;ll wait.</p>
<p>&hellip;</p>
<p>The clever bit about carrying a task over is taking the opportunity to evaluate it. If the task is really a five-minute thing, more often than not, I go ahead and take care of it right there and then. If it&rsquo;s a longer endeavor, the friction of writing it down again gives me the chance to answer the question of whether it&rsquo;s something I feel strongly about doing (and hence whether it&rsquo;s really important that I do it at all). It might not be, and that&rsquo;s fine. I cross it out and don&rsquo;t do it. If it is an important task, carrying it over means it remains front of mind until I can make the time to get it done.</p>
<h2 id="time-well-spent-doing">Time well spent doing</h2>
<p>I&rsquo;ve explored a myriad of task list apps, pre-printed to-do lists and journals, and all kinds of digital notes for tracking work. I consistently keep returning to the feel of pen on paper and an open notebook on my desk. Why? Minimal cognitive load.</p>
<p>No time spent categorizing and labeling tasks in a complicated system. No time spent remembering how to open that app, where you stored that <code>todo.txt</code> file, or deciding whether to write something down under your weekly or daily plan. No tasks lost in an invisible backlog that grows over the years, becoming more and more infeasible.</p>
<p>Just pen and paper, one page at a time, and the satisfaction of getting things done.</p>
]]></content></entry><entry><title type="html">Set up a Pi-hole VPN on an AWS Lightsail instance</title><link href="https://victoria.dev/archive/set-up-a-pi-hole-vpn-on-an-aws-lightsail-instance/"/><id>https://victoria.dev/archive/set-up-a-pi-hole-vpn-on-an-aws-lightsail-instance/</id><author><name>Victoria Drake</name></author><published>2021-10-07T11:01:13+00:00</published><updated>2021-10-07T11:01:13+00:00</updated><content type="html"><![CDATA[<p>I&rsquo;ve written a fair bit in the past about the <a href="/tags/privacy">whys of online privacy</a>, and <a href="/tags/security">a lot about staying safe online</a>. Chances are, if a search brought you here, you&rsquo;re well-past why. Let&rsquo;s go straight on to how.</p>
<p>This guide will walk you through setting up <a href="https://pi-hole.net/">Pi-hole</a> on an <a href="https://aws.amazon.com/lightsail/">AWS Lightsail</a> instance that acts as your VPN thanks to <a href="https://openvpn.net/">OpenVPN</a>. It&rsquo;s a more succinct version of the <a href="https://docs.pi-hole.net/guides/vpn/openvpn/overview/">official Pi-hole docs for OpenVPN</a>, made specifically for Lightsail with a few tips and tricks added in, because you deserve it.</p>
<h2 id="create-and-connect-to-a-lightsail-instance">Create and connect to a Lightsail instance</h2>
<ol>
<li>
<p>Log in or sign up to AWS and <a href="https://lightsail.aws.amazon.com/ls/webapp/home/instances">create a Lightsail Instance</a>.</p>
</li>
<li>
<p>Under <strong>Select a platform</strong>, choose <strong>Linux/Unix</strong>.</p>
</li>
<li>
<p>Under <strong>Select a blueprint</strong>, choose the <strong>OS Only</strong> button.</p>
</li>
<li>
<p>Select the latest <a href="https://docs.pi-hole.net/main/prerequisites/#supported-operating-systems">officially supported Ubuntu server</a>.</p>
</li>
<li>
<p>You can save a tidbit of effort by putting the following into the <strong>Launch script</strong> box:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span><span style="color:#75715e"># Update installed packages</span>
</span></span><span style="display:flex;"><span>sudo apt-get update
</span></span><span style="display:flex;"><span>sudo apt-get upgrade -y
</span></span></code></pre></div></li>
<li>
<p>Create a new SSH key for this server and ensure you download the <code>.pem</code>.</p>
</li>
<li>
<p>Choose your plan. The $3.50 USD instance is sufficient.</p>
</li>
<li>
<p>Give it a name then click <strong>Create instance</strong>.</p>
</li>
<li>
<p>Stare eagerly at the page until the instance status is <strong>Running</strong>, then go to the <strong>Networking</strong> tab.</p>
</li>
<li>
<p>Create a <a href="https://lightsail.aws.amazon.com/ls/docs/en_us/articles/understanding-static-ip-addresses-in-amazon-lightsail">Static IP</a> and attach it to your new instance. Remember that static IP addresses are free only while attached to an instance.</p>
</li>
<li>
<p>Click on your instance name to return to its dashboard. Go back to the <strong>Networking</strong> tab. It&rsquo;ll look a bit different now.</p>
</li>
<li>
<p>Under <strong>IPv6 networking</strong>, click the toggle to turn it off (unless you know what you are doing and you want IPv6 for some reason. Most of y&rsquo;all don&rsquo;t need it).</p>
</li>
<li>
<p>Under <strong>IPv4 Firewall</strong>, delete the rule for <code>HTTP</code>.</p>
</li>
<li>
<p>Click <strong>Add rule</strong>. In the <strong>Application</strong> dropdown, choose <strong>Custom</strong>.</p>
<ul>
<li>For <strong>Protocol</strong>, choose <strong>UDP</strong>.</li>
<li>In the <strong>Port or range</strong> input, enter a UDP port for the OpenVPN server to run on. (It&rsquo;s typically <code>1194</code>, which you can choose to use, but you might like a different number for security purposes. Port range is <code>0-65535</code>.)</li>
</ul>
</li>
<li>
<p>Connect using SSH and your new key pair, either <a href="https://lightsail.aws.amazon.com/ls/docs/en_us/articles/amazon-lightsail-ssh-using-terminal">in your terminal</a> or on the <strong>Connect</strong> tab with the <a href="https://lightsail.aws.amazon.com/ls/docs/en_us/articles/lightsail-how-to-connect-to-your-instance-virtual-private-server">browser-based client</a>.</p>
</li>
</ol>
<h2 id="install-openvpn-on-your-server">Install OpenVPN on your server</h2>
<p>After connecting to your server using SSH, install OpenVPN on your server.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span><span style="color:#75715e"># Download OpenVPN</span>
</span></span><span style="display:flex;"><span>wget https://git.io/vpn -O openvpn-install.sh
</span></span><span style="display:flex;"><span>chmod <span style="color:#ae81ff">755</span> openvpn-install.sh
</span></span><span style="display:flex;"><span>sudo ./openvpn-install.sh
</span></span></code></pre></div><p>You&rsquo;ll see:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>Welcome to this OpenVPN road warrior installer!
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>This server is behind NAT. What is the public IPv4 address or hostname?
</span></span><span style="display:flex;"><span>Public IPv4 address / hostname [x.xx.xxx.xxx]:
</span></span></code></pre></div><p>&hellip;where the default option is your static IP that you set up earlier. Hit return to accept this. Then:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>Which protocol should OpenVPN use?
</span></span><span style="display:flex;"><span>    1) UDP (recommended)
</span></span><span style="display:flex;"><span>    2) TCP
</span></span><span style="display:flex;"><span>Protocol [1]: 1
</span></span></code></pre></div><p>Choose <code>1</code> or hit return. Then:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>What port should OpenVPN listen to?
</span></span><span style="display:flex;"><span>Port [1194]: #####
</span></span></code></pre></div><p>Enter the UDP port number you chose earlier. Then:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>Select a DNS server for the clients:
</span></span><span style="display:flex;"><span>    1) Current system resolvers
</span></span><span style="display:flex;"><span>    2) Google
</span></span><span style="display:flex;"><span>    3) 1.1.1.1
</span></span><span style="display:flex;"><span>    4) OpenDNS
</span></span><span style="display:flex;"><span>    5) Quad9
</span></span><span style="display:flex;"><span>    6) AdGuard
</span></span><span style="display:flex;"><span>DNS server [1]: 1
</span></span></code></pre></div><p>Choose <code>1</code> or hit return. Then:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>Enter a name for the first client:
</span></span><span style="display:flex;"><span>Name [client]: pihole
</span></span></code></pre></div><p>The Pi-hole will be the client. Name it as you like then <code>Press any key to continue...</code></p>
<p>OpenVPN will set itself up. Confirm that <code>tun0</code> has the interface address <code>10.8.0.1/24</code> with the following command:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>ip addr show tun0
</span></span></code></pre></div><p>This ensures that the Pi-hole will be set up properly. Now, about that:</p>
<h2 id="install-and-configure-pi-hole">Install and configure Pi-hole</h2>
<p>On your Lightsail instance, install Pi-hole.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span><span style="color:#75715e"># Download and install Pi-hole</span>
</span></span><span style="display:flex;"><span>curl -sSL https://install.pi-hole.net | bash
</span></span></code></pre></div><p>This runs the Pi-hole automated installer. You&rsquo;ll see some prompts which you can answer using the enter key, arrow keys, tab, and space bar for selecting an option.</p>
<p>The important things:</p>
<ol>
<li>When you see <strong>Choose An Interface</strong>, ensure you pick <code>tun0</code>. It isn&rsquo;t the default selection.</li>
<li>You&rsquo;ll need to set the <strong>IPv4 address</strong> to the interface address you viewed previously using the <code>ip addr</code> command: <code>10.8.0.1/24</code>. This ensures the Pi-hole uses the VPN.</li>
</ol>
<blockquote>
<p><em>At time of writing,</em> the second item above wasn&rsquo;t presented as an option in the automated installer. After the Pi-hole installer finishes, manually change the IP address by editing the configuration file:</p>
<p><code>&gt; sudo vim /etc/pihole/setupVars.conf</code></p>
<p>Change the <code>IPV4_ADDRESS</code> to <code>10.8.0.1/24</code> and save the file. Restart the Pi-hole with: <code>pihole restartdns</code>.</p>
</blockquote>
<p>If you mess up, you can redo the configuration with <code>pihole reconfigure</code>.</p>
<p>Finally, you&rsquo;ll configure the VPN to use the Pi-hole.</p>
<h2 id="configure-openvpn">Configure OpenVPN</h2>
<p>Confirm the address of the <code>tun0</code> interface with:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>ip a | grep -C <span style="color:#ae81ff">1</span> <span style="color:#e6db74">&#39;tun0&#39;</span>
</span></span></code></pre></div><p>You should see: <code>inet 10.8.0.1/24</code> in there.</p>
<p>Edit the OpenVPN config file with:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>sudo vim /etc/openvpn/server/server.conf
</span></span></code></pre></div><p>Change the line that starts with <code>push &quot;dhcp-option</code>&hellip; to use the Pi-hole&rsquo;s IP address that you confirmed above:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-vim" data-lang="vim"><span style="display:flex;"><span><span style="color:#a6e22e">push</span> <span style="color:#e6db74">&#34;dhcp-option DNS 10.8.0.1&#34;</span>
</span></span></code></pre></div><p>If any other lines start with <code>push &quot;dhcp-option</code>&hellip;, comment those out.</p>
<p>If you want to log OpenVPN traffic, add these lines to the end of the 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;"><code class="language-vim" data-lang="vim"><span style="display:flex;"><span><span style="color:#a6e22e">log</span> <span style="color:#e6db74">/var/</span><span style="color:#a6e22e">log</span>/<span style="color:#a6e22e">openvpn</span>.<span style="color:#a6e22e">log</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">verb</span> <span style="color:#ae81ff">3</span>
</span></span></code></pre></div><p>Save the config. If you forgot to open Vim with <code>sudo</code>, use the <code>tee</code> trick: <code>:w !sudo tee %</code>, then <code>O</code>, then <code>:q!</code>.</p>
<p>Restart OpenVPN with <code>sudo systemctl restart openvpn-server@server</code>.</p>
<h3 id="configure-firewall">Configure firewall</h3>
<p>Run the following to control traffic to the server <a href="https://docs.pi-hole.net/guides/vpn/openvpn/firewall/">as described here</a>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>sudo iptables -I INPUT -i tun0 -j ACCEPT
</span></span><span style="display:flex;"><span>sudo iptables -A INPUT -i tun0 -p tcp --destination-port <span style="color:#ae81ff">53</span> -j ACCEPT
</span></span><span style="display:flex;"><span>sudo iptables -A INPUT -i tun0 -p udp --destination-port <span style="color:#ae81ff">53</span> -j ACCEPT
</span></span><span style="display:flex;"><span>sudo iptables -A INPUT -i tun0 -p tcp --destination-port <span style="color:#ae81ff">80</span> -j ACCEPT
</span></span><span style="display:flex;"><span>sudo iptables -A INPUT -p tcp --destination-port <span style="color:#ae81ff">22</span> -j ACCEPT
</span></span><span style="display:flex;"><span>sudo iptables -A INPUT -p tcp --destination-port <span style="color:#ae81ff">1194</span> -j ACCEPT
</span></span><span style="display:flex;"><span>sudo iptables -A INPUT -p udp --destination-port <span style="color:#ae81ff">1194</span> -j ACCEPT
</span></span><span style="display:flex;"><span>sudo iptables -I INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
</span></span><span style="display:flex;"><span>sudo iptables -I INPUT -i lo -j ACCEPT
</span></span><span style="display:flex;"><span>sudo iptables -P INPUT DROP
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Optionally, also block HTTPS advertisements while you&#39;re here.</span>
</span></span><span style="display:flex;"><span>sudo iptables -A INPUT -p udp --dport <span style="color:#ae81ff">80</span> -j REJECT --reject-with icmp-port-unreachable
</span></span><span style="display:flex;"><span>sudo iptables -A INPUT -p tcp --dport <span style="color:#ae81ff">443</span> -j REJECT --reject-with tcp-reset
</span></span><span style="display:flex;"><span>sudo iptables -A INPUT -p udp --dport <span style="color:#ae81ff">443</span> -j REJECT --reject-with icmp-port-unreachable
</span></span></code></pre></div><p>You can review the results with <code>sudo iptables -L --line-numbers</code>.</p>
<p><strong>These are only stored in memory</strong> before you save them, so test out your set up on your client now to see if it all works as expected.</p>
<h3 id="test-your-client-connection">Test your client connection</h3>
<p>To test your configuration, try adding a client (the phone or computer that will connect to the VPN).</p>
<ol>
<li>Run the OpenVPN script again: <code>sudo ./openvpn-install.sh</code> and choose <strong>1) Add a new client</strong>. Give it a name; you may find it helps to name it by the device, e.g. &ldquo;phone&rdquo;. This creates a file that ends in <code>.ovpn</code>. You need to place this file on your client to use it.</li>
<li>Install the appropriate <a href="https://duckduckgo.com/?q=OpenVPN+App">OpenVPN app</a> for your device.</li>
<li>Transfer the <code>.ovpn</code> file you just obtained to the device if you haven&rsquo;t already. (See <a href="#future-tasks">future tasks</a> for a way to copy the file to your host machine.) Follow instructions in your app (try under <strong>FAQ</strong>) for importing the <code>.ovpn</code> file and activating the VPN.</li>
<li>Ensure it seems to connect properly. If you <a href="https://duckduckgo.com/?t=ffab&amp;q=what's+my+ip">go to DuckDuckGo.com and search for &ldquo;What&rsquo;s my IP&rdquo;</a>, you should see the location of your Lightsail instance. For a more in-depth test, <a href="https://browserleaks.com/ip">check for DNS leaks at BrowserLeaks.com</a>.</li>
</ol>
<p>Try browsing for a while. You can also view the Pi-hole dashboard by visiting <code>http://pi.hole/admin/</code> on this device.</p>
<p>If everything seems all right, go on to saving the configuration on your instance.</p>
<h3 id="save-iptables">Save <code>iptables</code></h3>
<p>Save the <code>iptables</code> you created earlier using the <code>tee</code> command to achieve the second permission.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>sudo iptables-save | sudo tee /etc/pihole/rules.v4
</span></span></code></pre></div><p>You&rsquo;re finished with configuration on your Lightsail instance. If you wish to disconnect now, you can just type <code>exit</code>.</p>
<h2 id="future-tasks">Future tasks</h2>
<p>You&rsquo;re done with the set up! You now have your very own personal VPN with a Pi-hole keeping you safe from nasty trackers. Here are some references for operations you might like to come back to in the future:</p>
<ul>
<li>Reconnect to your Lightsail instance with SSH:
<ul>
<li><code>ssh -i /path/to/private-key.pem ubuntu@public-ip-address</code></li>
</ul>
</li>
<li>Set a password for the web interface dashboard:
<ul>
<li><code>pihole -a -p</code></li>
</ul>
</li>
<li>Access the web interface dashboard:
<ul>
<li>Connect to the VPN, then visit <code>http://pi.hole/admin/</code></li>
</ul>
</li>
<li>Update the Pi-hole:
<ul>
<li><code>pihole -up</code></li>
</ul>
</li>
<li>Add a new client (<a href="https://docs.pi-hole.net/guides/vpn/openvpn/clients/">for iOS, Linux, or Windows</a>, or <a href="https://docs.pi-hole.net/guides/vpn/openvpn/android-client/">for Android</a>)</li>
<li>Copy the <code>.ovpn</code> file for a client to your host machine (run on the host machine):
<ul>
<li><code>ssh -i /path/to/private-key.pem ubuntu@public-ip-address 'sudo cat /path/on/lightsail/client.ovpn' &gt; /path/on/host/client.ovpn</code></li>
</ul>
</li>
<li>Beef up that block list! Here&rsquo;s my favorite resource for updating your Pi-hole <a href="https://docs.pi-hole.net/database/gravity/#adlist-table-adlist">adlist table</a>: <a href="https://firebog.net/">The Big Blocklist Collection</a></li>
</ul>
<p>Enjoy your new, more secure and peaceful Internet! If you found this guide helpful, please share it with someone else.</p>
]]></content></entry><entry><title type="html">Beyond Gut Feelings: How I Use Issue Metrics to Boost Engineering Velocity</title><link href="https://victoria.dev/archive/beyond-gut-feelings-how-i-use-issue-metrics-to-boost-engineering-velocity/"/><id>https://victoria.dev/archive/beyond-gut-feelings-how-i-use-issue-metrics-to-boost-engineering-velocity/</id><author><name>Victoria Drake</name></author><published>2021-08-30T05:35:02+00:00</published><updated>2021-08-30T05:35:02+00:00</updated><content type="html"><![CDATA[<p>How long does it take for a bug to get squashed, or for a pull request to be merged? What kind of issues take the longest to close?</p>
<p>Most organizations want to improve productivity and output, but few technical teams seem to take a data-driven approach to discovering productivity bottlenecks. If you&rsquo;re looking to improve development velocity, a couple key metrics could help your team get unblocked. Here&rsquo;s how you can apply a smidge of data science to visualize how your repository is doing, and where improvements can be made.</p>
<h2 id="getting-quality-data">Getting quality data</h2>
<p>The first and most difficult part, as any data scientist would likely tell you, is ensuring the quality of your data. It&rsquo;s especially important to consider consistency: are dates throughout the dataset presented in a consistent format? Have tags or labels been applied under consistent rules? Does the dataset contain repeated values, empty values, or unmatched types?</p>
<p>If your repository has previously changed up processes or standards, consider the timeframe of the data you collect. If labeling issues is done arbitrarily, those may not be a useful feature. While cleaning data is outside the scope of this article, I can, at least, help you painlessly collect it.</p>
<p>I wrote a straightforward <a href="https://github.com/victoriadrake/got-issues/">Python utility</a> that uses the GitHub API to pull data for any repository. You can use this on the command line and output the data to a file. It uses the <a href="https://docs.github.com/en/rest/reference/issues#list-repository-issues">list repository issues endpoint (docs)</a>, which, perhaps confusingly, includes both issues and pull requests (PRs) for the repository. I get my data like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>$ python fetch.py -h
</span></span><span style="display:flex;"><span>usage: fetch.py <span style="color:#f92672">[</span>-h<span style="color:#f92672">]</span> <span style="color:#f92672">[</span>--token TOKEN<span style="color:#f92672">]</span> repository months
</span></span><span style="display:flex;"><span>$ python fetch.py OWASP/wstg <span style="color:#ae81ff">24</span> &gt; data.json
</span></span></code></pre></div><p>Using the GitHub API means less worry about standardization, for example, all the dates are expressed as ISO 8601. Now that you have some data to process, it&rsquo;s time to play with Pandas.</p>
<h2 id="plotting-with-pandas">Plotting with Pandas</h2>
<p>You can use a <a href="https://jupyter.org/">Jupyter Notebook</a> to do some simple calculations and data visualization.</p>
<p>First, create the Notebook 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;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>touch stats.ipynb
</span></span></code></pre></div><p>Open the file in your favorite IDE, or in your browser by running <code>jupyter notebook</code>.</p>
<p>In the first code cell, import Pandas and load your data:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py" data-lang="py"><span style="display:flex;"><span><span style="color:#f92672">import</span> pandas <span style="color:#66d9ef">as</span> pd
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>data <span style="color:#f92672">=</span> pd<span style="color:#f92672">.</span>read_json(<span style="color:#e6db74">&#34;data.json&#34;</span>)
</span></span><span style="display:flex;"><span>data
</span></span></code></pre></div><p>You can then run that cell to see a preview of the data you collected.</p>
<p>Pandas is a <a href="https://pandas.pydata.org/pandas-docs/stable/index.html">well-documented</a> data analysis library. With a little imagination and a few keyword searches, you can begin to measure all kinds of repository metrics. For this walk-through, here&rsquo;s how you can calculate and create a graph that shows the number of days an issue or PR remains open in your repository.</p>
<p>Create a new code cell and, for each item in your <a href="https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.html">Series</a>, subtract the date it was closed from the date it was created:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py" data-lang="py"><span style="display:flex;"><span>duration <span style="color:#f92672">=</span> pd<span style="color:#f92672">.</span>Series(data<span style="color:#f92672">.</span>closed_at <span style="color:#f92672">-</span> data<span style="color:#f92672">.</span>created_at)
</span></span><span style="display:flex;"><span>duration<span style="color:#f92672">.</span>describe()
</span></span></code></pre></div><p><a href="https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.describe.html"><code>Series.describe()</code></a> will give you some summary statistics that look something like these (from <a href="https://github.com/python/mypy">mypy on GitHub</a>):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>count                           514
</span></span><span style="display:flex;"><span>mean      5 days 08:04:17.239299610
</span></span><span style="display:flex;"><span>std      14 days 12:04:22.979308668
</span></span><span style="display:flex;"><span>min                 0 days 00:00:09
</span></span><span style="display:flex;"><span>25%          0 days 00:47:46.250000
</span></span><span style="display:flex;"><span>50%                 0 days 06:18:47
</span></span><span style="display:flex;"><span>75%          2 days 20:22:49.250000
</span></span><span style="display:flex;"><span>max               102 days 20:56:30
</span></span></code></pre></div><p><a href="https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.plot.html?"><code>Series.plot()</code></a> uses a specified plotting backend (<code>matplotlib</code> by default) to visualize your data. A histogram can be a helpful way to examine issue duration:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py" data-lang="py"><span style="display:flex;"><span>duration<span style="color:#f92672">.</span>apply(<span style="color:#66d9ef">lambda</span> x: x<span style="color:#f92672">.</span>days)<span style="color:#f92672">.</span>plot(kind<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;hist&#34;</span>)
</span></span></code></pre></div><p>This will plot a histogram that represents the frequency distribution of issues over days, which is one way you can tell how long most issues take to close. For example, mypy seems to handle the majority of issues and PRs within 10 days, with some outliers taking more than three months:</p>
<p><img src="plot.png" alt="Histogram for mypy issues over the last six months"></p>
<p>It would be interesting to visualize other repository data, such as its most frequent contributors, or most often used labels. Does a relationship exist between the author or reviewers of an issue and how quickly it is resolved? Does the presence of particular labels predict anything about the duration of the issue?</p>
<h2 id="you-aim-for-what-you-measure">You aim for what you measure</h2>
<p>Now that you have some data-driven superpowers, remember that it comes with great responsibility. Deciding what to measure is just as, if not more, important than measuring it.</p>
<p>Consider how to translate the numbers you gather into productivity improvements. For example, if your metric is closing issues and PRs faster, what actions can you take to encourage the right behavior in your teams? I&rsquo;d suggest encouraging issues to be clearly defined, and pull requests to be small and have a well-contained scope, making them easier to understand and review.</p>
<p>To prepare to accurately take measurements for your repository, establish consistent standards for labels, tags, milestones, and other features you might want to examine. Remember that meaningful results are more easily gleaned from higher quality data.</p>
<p>Finally, have fun exercising your data science skills. Who knows what you can discover and improve upon next!</p>
]]></content></entry><entry><title type="html">There are better options for a privacy-respecting phone</title><link href="https://victoria.dev/archive/there-are-better-options-for-a-privacy-respecting-phone/"/><id>https://victoria.dev/archive/there-are-better-options-for-a-privacy-respecting-phone/</id><author><name>Victoria Drake</name></author><published>2021-08-11T11:37:35+00:00</published><updated>2021-08-11T11:37:35+00:00</updated><content type="html"><![CDATA[<p>Whether you think the news of Apple scanning your private devices was a big deal, run-of-the-mill, or something we all should have seen coming, you might be wondering, &ldquo;What now?&rdquo; We know full well that Google is looking at the stuff on your phone too (and Gmail, and&hellip; well, everywhere else) so it’s not like there are other options after Apple&hellip; right?</p>
<p>If a move towards privacy is what we’re after, we know a new off-the-shelf Google phone isn&rsquo;t a better answer &ndash; but there are more options.</p>
<p>If you don&rsquo;t want the details, jump straight to <a href="#the-tldr">The TL;DR</a> at the end.</p>
<h2 id="linux-phones-sort-of">Linux phones (sort of)</h2>
<p>Unless you’re a rather tolerant tech-savvy tinkerer, a Linux phone isn’t one of these options&hellip; yet. I’ve personally been very excited about the bevy of emerging options in this space, from freedom-oriented hardware to fully open source, crowd-developed operating systems.</p>
<p>The current state of these efforts is that this magical mashup just isn’t ready yet. Most Linux phone OS such as Ubuntu Touch, Mobian, Pure OS, etc, are in a &ldquo;mostly working&rdquo; state, with the missing features ranging from &ldquo;lack of reliable push notifications&rdquo; to &ldquo;intermittent Bluetooth connectivity&rdquo; to &ldquo;camera.&rdquo;</p>
<p>If all you need is text messaging and a web browser, yes, you can probably go this route. For most users however, this isn’t going to make daily-driver status.</p>
<p>If a Linux phone would suit you, I recommend getting your hands on a <a href="https://www.pine64.org/pinephone/">PinePhone</a> and running Arch Linux ARM (<a href="https://github.com/dreemurrs-embedded/Pine64-Arch/releases">releases on GitHub</a>) with <a href="https://www.plasma-mobile.org/get/">Plasma Mobile</a>.</p>
<h2 id="de-googled-android">De-googled Android</h2>
<p>For a daily-driver, &ldquo;de-googled&rdquo; Android is your best bet. Android itself (specifically, the Android Open Source Project source code) is based on a modified Linux kernel and is free and open source software. When we typically think of &ldquo;Android phones,&rdquo; we refer to Android devices with Google&rsquo;s proprietary software added to the mix, including Google Play Services. A &ldquo;de-googled&rdquo; Android phone is essentially the Android OS without Google&rsquo;s <del>spyware</del> services included by default.</p>
<p>Keep in mind that this route still involves some DIY. You&rsquo;ll need to install an OS on a device yourself. Don&rsquo;t worry, there are step-by-step guides available &ndash; the most technical thing you&rsquo;ll likely have to do is copy and paste some commands into your terminal.</p>
<p>Free and open source Android OS comes in multiple flavors, and the choice isn&rsquo;t arbitrary. Your selection of a &ldquo;de-googled&rdquo; phone is going to be determined by a couple factors: the hardware device you have or that you want to use, and the apps (software) you want to run on it.</p>
<h3 id="hardware">Hardware</h3>
<p>The phone you may already have (or the one you’re willing to purchase) will influence your choice of operating system (OS).</p>
<h4 id="lineageos">LineageOS</h4>
<p>At the time I’m writing this, if you have an older Pixel or another model of Android phone, your best bet for a hassle-free OS with A-class support will be Lineage. Here’s a link to the <a href="https://wiki.lineageos.org/devices/">LineageOS list of supported devices</a>. Clicking on your device here will get you to some installation instructions for your phone.</p>
<h4 id="grapheneos">GrapheneOS</h4>
<p>If you have a newer Pixel (generation 3 up to the newer 5) then GrapheneOS could be the way to go. <a href="https://grapheneos.org/faq#supported-devices">Here are the devices officially supported by GrapheneOS</a>. They also have <a href="https://grapheneos.org/install/">easy-to-follow installation instructions</a> and help via chat. It is possible to run GrapheneOS on other phones, but not without substantial DIY for which technical knowledge would help.</p>
<p>Generally speaking, GrapheneOS is intended to be a security-hardened operating system targeted at individuals who won&rsquo;t be miffed if there are tradeoffs for mitigating vulnerabilities. If you don&rsquo;t have those requirements or intend to use Google Apps on your phone (see Software), then LineageOS will likely suit you better.</p>
<h4 id="new-phone-who-dis">New phone, who dis?</h4>
<p>If you&rsquo;re looking to purchase a new phone, you have some flexibility. My general recommendation is to pick up last-season&rsquo;s version of the model you want. Not only will this likely be cheaper (and often a great deal if you buy refurbished) but the open source community that develops these operating systems will have had more time to work with the device itself, which could help ensure better compatibility and a smoother set up.</p>
<p>Consider buying a refurbished phone (sometimes called &ldquo;renewed&rdquo;) locally when you can. This can help fund the small businesses that offer them.</p>
<h3 id="software">Software</h3>
<p>What do you <em>need</em> to do on your phone? Privacy and convenience are typically at odds (a far larger topic I won’t dig into right now) so it can help to narrow down the functionality you need. If your needs look something like:</p>
<ul>
<li>Calls and texts</li>
<li>Web browser</li>
<li>Web-based email via browser</li>
</ul>
<p>Then you&rsquo;re good to go, right out of the box, with either LineageOS or GrapheneOS. They&rsquo;ll both include free and open source apps that let you do all these things.</p>
<p>If you want a particular application that doesn&rsquo;t come pre-installed, here&rsquo;s where we get into some nuance. Your choices depend on the level of privacy you&rsquo;d like to maintain. Here are your avenues for installing apps, listed in order of preference.</p>
<h4 id="1-official-apks">1. Official APKs</h4>
<p>Some particularly privacy-focused applications offer an Android Package Kit (APK) that you can download directly in order to install the app. You should only download these when you&rsquo;ve navigated directly to a domain that the organization owns. Here are my favorites:</p>
<ul>
<li><a href="https://signal.org/android/apk/">Signal</a></li>
<li><a href="https://protonapps.com/protonmail-android">ProtonMail</a></li>
</ul>
<p>You can download and install APKs whether you choose LineageOS or GrapheneOS.</p>
<h4 id="2-use-f-droid">2. Use F-Droid</h4>
<p>If you can&rsquo;t find an APK for something you want, search for it on F-Droid.</p>
<p>The <a href="https://f-droid.org/">F-Droid</a> software repository allows you to download and install apps in much the same way that the Google Play store does, with a couple notable differences. All the apps here are free and open source, and no account or profile is required to download them. The F-Droid APK itself can be downloaded and installed from f-droid.org directly on either LineageOS or GrapheneOS.</p>
<p>Just like any open source software, it&rsquo;s up to the user (you) to ensure that you&rsquo;re downloading and installing software you trust. If you want help or advice, F-Droid has a healthy community that you can interact with in lots of ways, including <a href="https://f-droid.org/en/about/">via IRC, Matrix, and the Fediverse</a>.</p>
<p>You can find an app for pretty much anything here: from your general-store type functions such as to-do lists, music players, and maps; to specific niche security applications, and even a tea timer. Here are some well-known choices I can easily recommend:</p>
<ul>
<li><a href="https://f-droid.org/en/packages/com.standardnotes/">Standard Notes</a> (<a href="https://standardnotes.com">https://standardnotes.com</a>)</li>
<li><a href="https://f-droid.org/en/packages/im.vector.app/">Element for Matrix</a> (<a href="https://element.io/">https://element.io/</a>)</li>
<li><a href="https://f-droid.org/en/packages/de.tutao.tutanota/">Tutanota</a> (<a href="https://tutanota.com">https://tutanota.com</a>)</li>
</ul>
<h4 id="3-aurora-store">3. Aurora Store</h4>
<p>If you need an app that isn&rsquo;t available on F-Droid, your next stop is the Aurora Store. This is an unofficial client for the Google Play Store that lets you download free applications anonymously, without signing into a Google account. Most applications found in the larger stores can be downloaded this way, without requiring Google&rsquo;s proprietary stuff on your phone.</p>
<p>When loading Aurora Store for the first time, be sure to choose the &ldquo;Anonymous&rdquo; option instead of signing in.</p>
<p>The Aurora Store itself can be <a href="https://f-droid.org/en/packages/com.aurora.store/">installed via F-Droid</a> or <a href="https://auroraoss.com/downloads/AuroraStore/">auroraoss.com</a>. It works on either LineageOS or GrapheneOS &ndash; however, apps that require less private permissions or access will probably work better on LineageOS.</p>
<p>Keep in mind that your phone OS in no way supports these apps directly, or knows what&rsquo;s in them, or what sort of tracking and information exchange they may be up to. It&rsquo;s a slight privacy downgrade, but still better than a fully Google-ified OS.</p>
<h4 id="4-if-you-need-google-apps">4. If you need Google Apps</h4>
<p>If this will be your only phone and you simply must have Google Apps on it (think Google Play Store, Gmail, Calendar, Photos, etc) then go with LineageOS. You can choose to try emulating Google Play Services using <a href="https://lineage.microg.org/">LineageOS for microG</a>, or <a href="https://wiki.lineageos.org/gapps">install the Google Apps add-on</a> when you install LineageOS.</p>
<h2 id="the-tldr">The TL;DR</h2>
<p>Here&rsquo;s the &ldquo;Internet personality quiz&rdquo; version of everything above. You are&hellip;</p>
<ol>
<li><strong>Knowledgeable about Linux;</strong> mostly use a phone for text, calls, and web browser; and potentially want to help develop Linux phone software.
<ul>
<li>Try a Linux phone such as the PinePhone, but consider one of the other options as a back up for when you just need stuff to work.</li>
</ul>
</li>
<li><strong>Security or privacy inclined,</strong> happy to use FOSS apps, or do most things via web browser anyway.
<ul>
<li>Get your hands on a Pixel 3{XL, a, a XL}, Pixel 4{XL, a, a 5G}, or Pixel 5, and use GrapheneOS. <a href="https://grapheneos.org/install/">Installation instructions here</a>.</li>
<li>Optionally, download the <a href="https://f-droid.org/">F-Droid</a> or <a href="https://f-droid.org/en/packages/com.aurora.store/">Aurora Store</a> APKs for apps.</li>
</ul>
</li>
<li><strong>Someone who needs Google Apps to work,</strong> or you want a phone that isn&rsquo;t a Pixel, or you&rsquo;re setting up a device for someone who&rsquo;s fine using Android but needs it to look familiar.
<ul>
<li>Use LineageOS with any of <a href="https://wiki.lineageos.org/devices/">its supported devices</a>. Click on the device name for installation instructions.</li>
<li>If you must have Google Apps and need Google Play Services to work, <a href="https://wiki.lineageos.org/gapps">install the add-on</a> at the same time you install LineageOS.</li>
<li>Optionally, download the <a href="https://f-droid.org/">F-Droid</a> or <a href="https://f-droid.org/en/packages/com.aurora.store/">Aurora Store</a> for installing apps.</li>
</ul>
</li>
</ol>
<p>Whichever route you choose, my advice is to treat this like a learning experiment. You&rsquo;re sort of building your own phone, after all, and gaining all the technological independence that comes with that knowledge. If possible, don&rsquo;t ditch your current phone until you try out one (two?) of these paths. The one you end up liking most could surprise you! It&rsquo;s great to have options.</p>
]]></content></entry><entry><title type="html">The Doorway Problem: Why Building in Isolation Fails</title><link href="https://victoria.dev/posts/the-doorway-problem-why-building-in-isolation-fails/"/><id>https://victoria.dev/posts/the-doorway-problem-why-building-in-isolation-fails/</id><author><name>Victoria Drake</name></author><published>2021-08-09T03:17:49+00:00</published><updated>2021-08-09T03:17:49+00:00</updated><content type="html"><![CDATA[<p>It’s a comedy classic—you’ve got a grand idea. Maybe you want to build a beautiful new dining room table. You spend hours researching woodcraft, learn about types of wood and varnish, explore different styles of construction, and now you have a solid plan. You buy the wood and other materials. You set up in the garage. For months you measure and saw, sand, hammer and paint. Finally, the effort pays off. The table is finished, and it’s fantastic.</p>
<p>In a frenzy of accomplishment you drag it into the house—only to discover that your dining room doorway is several inches too small. It doesn’t fit.</p>
<p>You might say this comedic example is unrealistic. Of course an experienced DIY-er would have measured the doorway first. But in real life, unforeseen problems rarely come solo. Once you finally get the table through the door (after removing the legs and reassembling it inside), you discover the floor’s uneven. The chairs you chose are a few inches too short. The ceiling light hangs too low. Each solution creates new problems you never anticipated.</p>
<p>I’ve seen this exact pattern play out dozens of times in software development, just with different furniture. Teams spend months building features in isolation, only to discover they don’t fit through the “doorways” of real user workflows, existing infrastructure, or business constraints. The solution isn’t better planning—it’s building in context from the start.</p>
<h2 id="the-planning-fallacy-or-why-were-all-terrible-at-this">The Planning Fallacy (Or: Why We’re All Terrible at This)</h2>
<p>Few software developers are accurate when it comes to time and cost estimates. This isn’t a failing of engineers specifically—it’s a deeply human tendency toward optimism when predicting our own future. First proposed by Daniel Kahneman and Amos Tversky in 1979, the planning fallacy explains why our estimates are consistently wrong.</p>
<p>In one study, students were asked to estimate how long they’d take to finish their senior theses. The estimates averaged 27.4 days at the optimistic end and 48.6 days at the pessimistic end. The actual completion time? 55.5 days. Even the pessimistic estimates were too optimistic.</p>
<p>The researchers proposed two main reasons: first, people focus on their future plans rather than their past experiences; second, people don’t think past experiences matter much to the future anyway.</p>
<p>You can probably find examples of this in your own recent project history. Sure, that last “two-day feature” turned into a two-week affair, but that was only because the API documentation was wrong. Or maybe you didn’t finish that database migration when planned, but that was only because you discovered the staging environment was configured differently than production. You’re absolutely, positively, definitely certain that next time will be different.</p>
<blockquote>
<p>The reality is that we’re terrible at factoring in the unexpected daily demands of building software.</p>
</blockquote>
<p>Legacy code behaves mysteriously. Third-party services have undocumented quirks. Staging environments don’t match production. Users do things we never anticipated. Some measure of ignorance about these complications probably keeps us sane enough to start new projects.</p>
<p>But some measure of accurate planning is also necessary for success. The solution is working in context as much as possible, rather than trying to plan for every contingency.</p>
<h2 id="context-is-your-reality-check">Context Is Your Reality Check</h2>
<p>Let’s reconsider the dining room table story. Instead of spending months out in the garage, what would you do differently to build in context?</p>
<p>You might say, “Build it in the dining room!” While that would be ideal for context, it’s rarely possible in homes or software development. Instead, you do the next best thing: start building, and make frequent visits to context.</p>
<p>Having decided you want to build a table, one of the first questions is “How big will it be?” You’ll have requirements to fulfill (must seat six, must match other furniture, must hold the weight of your annual twenty-eight-course Christmas feast) that lead you to a rough decision.</p>
<p>With a size in mind, you build a mock-up. At this point, the specific materials, style, and color don’t matter—only the three dimensions. Once you have your mock table, you can make your first trip to the context where it will ultimately live. Attempting to carry your foam/wood/cardboard/balloon animal mock-up into the dining room will reveal issues you never considered, and possibly new opportunities as well. Perhaps, though you’d never have thought it, a modern abstractly-shaped dining table would better complement the space. You can take this into account in your next higher-fidelity iteration.</p>
<p>This translates directly to software development, minus the Christmas feast. You may recognize this as the MVP approach, but even here, putting the MVP in context is a step that’s frequently omitted.</p>
<p>I’ve seen teams spend months building a “simple” user authentication system, only to discover that their company’s SSO provider doesn’t support the OAuth flow they built around. Or teams that create beautiful interfaces that completely break when real user data (with its inconsistent formats and edge cases) gets loaded. Where will your product ultimately live? How will it be accessed? What does real data look like?</p>
<blockquote>
<p>Building your MVP and attempting to deploy it with realistic constraints will uncover these issues when they’re still manageable.</p>
</blockquote>
<p>Even when teams have prior experience with technologies, remember the planning fallacy. People naturally discount past evidence to the point of forgetting. It’s also unlikely that the same exact team is building the same exact product as last time. The language, technology, framework, and infrastructure have likely changed—as have the capabilities and bandwidth of the engineers. Frequent visits to context help you run into issues early, adapt to them, and create short feedback loops.</p>
<h2 id="go-for-good-enough-then-iterate">Go for Good Enough (Then Iterate)</h2>
<p>The specific meaning of putting something in context varies from project to project. It might mean deploying to cloud infrastructure, running on a new server, or testing whether your remote office can access the same resources you use. In all cases, keep those short iterations going. Don’t wait to get a version to 100% before finding out if it works in context. Ship it at 80%, see how close you got, then iterate.</p>
<p>This approach feels risky if you’re used to planning everything upfront. But the alternative—discovering fundamental incompatibilities after months of work—is much riskier. Better to learn that your table won’t fit through the door when it’s still made of cardboard than when it’s solid oak.</p>
<p>The best software gets built by teams that understand the difference between the theoretical problem they’re solving and the real environment where their solution needs to work. Context is messy, unpredictable, and full of constraints you never anticipated. That’s exactly why you need to visit it early and often.</p>
<p>Your garage is perfect for focused work, but your dining room is where people actually eat dinner. Build for where your software will really live, not where it’s convenient to develop it.</p>
]]></content></entry><entry><title type="html">How to Think Like a Hacker (And Why Your Team Should Too)</title><link href="https://victoria.dev/posts/how-to-think-like-a-hacker-and-why-your-team-should-too/"/><id>https://victoria.dev/posts/how-to-think-like-a-hacker-and-why-your-team-should-too/</id><author><name>Victoria Drake</name></author><published>2021-07-27T04:26:26-04:00</published><updated>2021-07-27T04:26:26-04:00</updated><content type="html"><![CDATA[<p>The most effective security-minded developers I know share one trait: they’re professionally suspicious of their own assumptions. They look at a form field and wonder what happens if someone tries to enter something unexpected. They design an API endpoint and ask how someone might misuse it. They have a systematic curiosity about how systems behave versus how they’re supposed to behave.</p>
<p>I saw this firsthand while working with a team where questioning assumptions became a regular part of our code review process. We’d look at every new feature and ask “How might someone abuse this?” I developed a particular talent for finding injection attacks on forms—apparently I have a knack for thinking of creative ways to sneak SQL queries into text fields. After the third or fourth time I caught these vulnerabilities during review, we added validation middleware to eliminate that entire class of problems.</p>
<p>But the real breakthrough was watching how the team’s thinking evolved. Once developers got used to questioning their assumptions about user behavior, they started writing more robust solutions from the start. Security thinking became a starting point rather than something bolted on afterward.</p>
<h2 id="designing-for-reality-not-just-intent">Designing for Reality, Not Just Intent</h2>
<p>One of the most effective practices we developed was specifying both the “happy path” and the “unhappy path” during our design process. The happy path was straightforward—everything happens in the way and sequence we intended. But the unhappy paths were where we learned the most: what happens when steps occur out of order? When data is missing or provided in an unexpected format? When external systems fail at exactly the wrong moment?</p>
<p>This dual-path thinking transformed how we approached every feature. Instead of just asking “How should this work?” we started asking “How will this actually be used?” and “What should happen when reality doesn’t match our expectations?” It sounds pessimistic, but it actually made development more fun. It caused us to think about our application from all angles rather than just implementing obvious functionality.</p>
<p>The unhappy path exercise revealed assumptions we didn’t even know we were making. We’d design a user registration flow assuming people would fill out forms completely and submit them once. Then we’d consider reality: What if someone submits the form multiple times? What if they navigate away and come back? What if they fill out the form, wait an hour, then submit it after their session expires?</p>
<p>Each unhappy path scenario led to better design decisions. Race condition handling. Idempotent endpoints. Graceful degradation when external services are unavailable. The code that protected against malicious users also handled legitimate users experiencing network glitches or browser crashes.</p>
<h2 id="systematic-questioning-as-a-superpower">Systematic Questioning as a Superpower</h2>
<p>There’s a particular mindset that effective security thinking requires—call it systematic skepticism. It’s the ability to look at any system and ask “What assumptions is this making?” and “What happens when those assumptions are wrong?” This kind of thinking makes your software more robust.</p>
<p>Sometimes this means channeling your inner four-year-old—pushing every button, ignoring all instructions, using things in ways their makers never intended. But rather than random exploration, you develop structured ways of challenging system boundaries, finding edge cases, and being creative about the ways that software can be used beyond its intended purpose.</p>
<p>This systematic questioning makes you better at every aspect of development. When you’re used to thinking about edge cases and unexpected inputs, you write more defensive code naturally. When you habitually consider what could go wrong, you build better (more useful) error handling. When you assume users will do unexpected things, you design more intuitive interfaces.</p>
<p>I’ve noticed that developers who adopt this questioning mindset become significantly better at debugging production issues too. Instead of being surprised when something breaks, they’re already thinking “What unexpected condition triggered this?” They approach problems with methodical curiosity rather than frustrated confusion.</p>
<h2 id="building-a-culture-of-constructive-skepticism">Building a Culture of Constructive Skepticism</h2>
<p>The key to building security-conscious teams isn’t teaching people to be afraid of attackers—it’s helping them develop genuine curiosity about system behavior under stress. When questioning assumptions becomes intellectually interesting rather than anxiety-inducing, your team will start doing it automatically.</p>
<p>Code reviews become more engaging when everyone is looking for unspoken assumptions about user behavior. Feature planning gets more thorough when “What are the unhappy paths?” is a standard question alongside “What should it do?” Architecture discussions become more robust when you’re considering not just how systems should work together, but how they should behave when dependencies are slow, unavailable, or returning unexpected data.</p>
<p>The practical implementation is surprisingly straightforward. During development, encourage your team to spend time being deliberately unreasonable with whatever they’re building. During design reviews, spend equal time on happy and unhappy paths. During testing, encourage your team to think like someone who’s never seen your application before and doesn’t understand the rules.</p>
<p>What emerges is a team that builds more resilient systems without extra effort. When you’re accustomed to thinking about failure modes, you naturally design systems that handle them gracefully. When you expect users to ignore instructions, you build interfaces that guide them toward success even when they’re not following the intended flow.</p>
<h2 id="security-as-engineering-excellence">Security as Engineering Excellence</h2>
<p>What I’ve learned is that security thinking is really just rigorous engineering thinking with a creative twist. It’s the same mental process you use when debugging complex issues or designing APIs that won’t confuse future developers. You’re considering multiple perspectives, anticipating edge cases, and designing for resilience rather than just functionality.</p>
<p>The most successful security-conscious teams I’ve worked with don’t have dedicated security experts who review everything after the fact—they have developers who think about security implications as naturally as they think about performance or usability. This happens through cultural reinforcement and consistent practice, not through mandates or compliance checklists.</p>
<p>The payoff extends far beyond security. Teams that think about unhappy paths build more reliable software. Developers who consider malicious inputs write better input validation for legitimate users. Engineers who design for system failures create more robust integrations. The skills reinforce each other in ways that make everyone more effective.</p>
<p>Most importantly, this approach makes engineering work more intellectually satisfying. There’s something deeply rewarding about anticipating problems and solving them before they happen. When your team develops the habit of systematically questioning their assumptions, they’ll approach every problem with the kind of methodical curiosity that leads to truly robust solutions.</p>
<p>You can help your team become professionally curious about system boundaries, failure modes, and the gap between how software is supposed to work and how it actually gets used. Once they develop that mindset, they’ll write more secure code naturally, because they’ll view software the same way attackers do—as systems that can fail when someone does something unexpected.</p>
]]></content></entry><entry><title type="html">A GitHub guide for non-technical leaders</title><link href="https://victoria.dev/archive/a-github-guide-for-non-technical-leaders/"/><id>https://victoria.dev/archive/a-github-guide-for-non-technical-leaders/</id><author><name>Victoria Drake</name></author><published>2021-05-24T00:00:00+00:00</published><updated>2021-05-24T00:00:00+00:00</updated><content type="html"><![CDATA[<p>As I write this, the front page of GitHub.com declares in big bold letters that this is &ldquo;Where the world builds software.&rdquo; This is true. In technology companies today, the creation of your product is largely happening where your developers spend time. It&rsquo;s where big and small product decisions are made every day &ndash; the kind of decisions that, wittingly or not, will decide the future of your company.</p>
<p>I&rsquo;m writing this guide for a very specific person &ndash; possibly you, or someone you know. I&rsquo;ll explain how a non-technical business leader can find information and take part in the decisions and questions that happen only on GitHub. You don&rsquo;t need to know how to use Git. You just need a few minutes to follow along, and a desire to be a resource and servant leader for your teams. Let&rsquo;s do it!</p>
<p>If you haven&rsquo;t signed up yet, click below to read the very first steps. Once you&rsquo;re logged in, read on to join in!</p>
<details>
<summary>The very first steps</summary>
<p>GitHub comes in two different flavors: GitHub Enterprise, or GitHub.com. If your team uses GitHub.com, you can <a href="https://github.com/join">sign up here</a> using your work email.</p>
<p>With GitHub Enterprise, signing up depends on your individual company&rsquo;s configuration. For example, you may be set up to log in with SAML single sign-on (such as through your GSuite credentials). Get in touch with the folks administering Enterprise in order to get signed up or logged in.</p>
<p>For the rest of this guide, it doesn&rsquo;t matter if you&rsquo;re using GitHub Enterprise or GitHub.com &ndash; they&rsquo;re largely the same. Just ensure you get connected to your company&rsquo;s Organization or Team, if there is one. Someone with administrative privileges needs to invite you using the email you signed up with.</p>
</details>
<h2 id="where-the-magic-happens">Where the magic happens</h2>
<p>On GitHub, work is typically grouped by projects or products into what&rsquo;s called <strong>repositories</strong>. Your team or company may have just one of these that they regularly use (they might call it a &ldquo;monorepo&rdquo;) or several repositories that represent different technical components of a single product.</p>
<p>Once you log in, you&rsquo;ll be on the <strong>Recent activity</strong> page. You can search for the name of the repository you want to visit in the search bar at the top left. If your company&rsquo;s repositories are private, you may need to be invited by an administrator in order to view it.</p>
<p><img src="search.png" alt="Example search"></p>
<p>When you view a repository, it looks like this. I&rsquo;ve pointed out some of the important bits.</p>
<p><img src="repo.png" alt="Screenshot of the OWASP Web Security Testing Guide repository"></p>
<p>In any repository, there are two main areas where decisions usually take place. These are in <strong>Issues</strong> and <strong>Pull Requests</strong>, and you&rsquo;ll mainly focus your attention here. Click on the <strong>Issues</strong> tab to see these.</p>
<p>You&rsquo;ll be presented with a list of Issues, which you can think of as individual topics. This format is essentially a discussion board. Clicking on any of the Issue titles will take you to its thread.</p>
<p><img src="issues.png" alt="Issues page screenshot"></p>
<p>Here&rsquo;s where the magic happens! Folks on your team use Issues to discuss all kinds of topics. These may be a very technical and esoteric cost-benefit analysis, or a fundamental customer-facing design decision. A quick read of the first message in the thread is likely to reveal whether it&rsquo;s a decision that could use your help.</p>
<p>Issues are a starting point for work. Here, team members make decisions about the type and scope of a change they plan to make.</p>
<p>When someone has their changes ready, they&rsquo;ll open a Pull Request so that other team members can preview and give input on those changes before they become part of the repository. Click the <strong>Pull Request</strong> tab at the top to view these.</p>
<p>You&rsquo;re presented with a very similar view on this page &ndash; yes, it&rsquo;s another discussion board! You can click on any Pull Request to view its thread as well.</p>
<p>Pull Requests have some additional tabs that team members use for code reviews. All the conversation will show up in the <strong>Conversation</strong> tab.</p>
<p><img src="pr.png" alt="Screenshot of Pull Request"></p>
<h2 id="sorting-out-whats-relevant">Sorting out what&rsquo;s relevant</h2>
<p>A lot of discussion happens in Issues and Pull Request threads, and not all of it may be relevant for you to look at. Thankfully, GitHub has some excellent collaboration tools that can help your team direct your attention to where it&rsquo;s most needed. These are <a href="https://github.blog/2011-03-23-mention-somebody-they-re-notified/">@mentions</a> and labels.</p>
<h3 id="ask-to-be-mentioned">Ask to be @mentioned</h3>
<p>These work the same way on GitHub as they do on Twitter. When someone @mentions you, you&rsquo;ll receive a notification via email, or you&rsquo;ll see it in your <a href="https://github.com/notifications">Notification Center</a> when you&rsquo;re logged in. This depends on your notification settings, which you should adjust to your liking. If you only want to be notified when someone @mentions you or replies to you, you should uncheck everything on the <a href="https://github.com/settings/notifications">Notification Settings page</a> except for your preferred options under <strong>Participating</strong>.</p>
<p><img src="participating.png" alt="Notification Settings for @mentions"></p>
<p>Now when someone references you in a discussion on GitHub, you&rsquo;ll be notified and you&rsquo;ll have the chance to respond!</p>
<h3 id="using-labels">Using labels</h3>
<p>Another less direct way to see where you can effectively contribute is to ask your team to use labels. You may recall seeing these in the right sidebar of Issue and Pull Request threads:</p>
<p><img src="labels.png" alt="Screenshot of labels in the right sidebar"></p>
<p>You can create different labels to categorize a discussion, and you can apply as many labels to a discussion as you like. In order to have your team draw your attention to threads that might benefit from your input or guidance, ask folks to use a label to point these out. This could be the <code>question</code> label, or any new label of your choosing.</p>
<p>Clicking on a label in the sidebar will take you to a page that shows all the Issues or Pull Requests with that label. The URL will look something like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>https://github.com/&lt;organization name&gt;/&lt;repository name&gt;/labels/question
</span></span></code></pre></div><p>You can also bookmark this page to easily check it on a regular schedule. This is a great, low-friction way for your team to indicate areas that could use your input.</p>
<h2 id="collaborating-on-files">Collaborating on files</h2>
<p>Similar to tracking changes in Google Docs or Word documents, you can edit documents in GitHub in a way that lets your team see the changes you&rsquo;ve made. This is a fantastic method for collaborating with your team where they work, and avoids the hassle of emailing attachments around.</p>
<p>Text files in repositories have extensions such as <code>.md</code> for Markdown, or <code>.txt</code> for plain text. The majority of documentation on GitHub is in Markdown format. Clicking on any file in the repository file list will open it on the GitHub website. At the top right of the document, look for these buttons:</p>
<p><img src="doc.png" alt="Document actions"></p>
<p>Clicking on the pencil icon will let you edit the file right there in your web browser. You may not see anything special as you&rsquo;re typing, but once you commit (like saving) your file, all your changes are tracked with Git! For a step-by-step guide to editing, see <a href="https://docs.github.com/en/github/managing-files-in-a-repository/managing-files-on-github/editing-files-in-your-repository">Editing files in your repository</a>.</p>
<p>Here are some helpful <a href="https://docs.github.com/en/github/writing-on-github">articles for formatting text with Markdown on GitHub</a>.</p>
<h2 id="proactive-participation">Proactive participation</h2>
<p>GitHub is well-structured as a collaboration platform. That&rsquo;s why people of all professions use it not just for software development, but also for networking, getting jobs and sponsorships, and even for hosting simple no-code websites. My own company uses GitHub for everything from collaborating on company documentation to automated Change Control Board processes for FedRAMP.</p>
<p>At your leisure, I encourage you to chase your curiosity and explore. Don&rsquo;t be shy about asking questions, or asking technical folks on your team to explain something if you think it will enable you to be a bigger help to them. With so much of the world building software on GitHub, there&rsquo;s a lot you can contribute when you&rsquo;re where the work happens.</p>
]]></content></entry><entry><title type="html">Digital resilience: redundancy for websites and communications</title><link href="https://victoria.dev/archive/digital-resilience-redundancy-for-websites-and-communications/"/><id>https://victoria.dev/archive/digital-resilience-redundancy-for-websites-and-communications/</id><author><name>Victoria Drake</name></author><published>2021-02-22T04:00:43-05:00</published><updated>2021-02-22T04:00:43-05:00</updated><content type="html"><![CDATA[<p>When what seems like half the planet noped out of WhatsApp after its terms of service update, applications like <a href="https://signal.org/download/">Signal</a> (which I highly recommend) saw an unprecedented increase in user traffic. Signal had so many new users sign up that it overwhelmed their existing infrastructure and lead to a 24-hour-ish outage.</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Signal is experiencing technical difficulties. We are working hard to restore service as quickly as possible.</p>&mdash; Signal (@signalapp) <a href="https://twitter.com/signalapp/status/1350118809860886528?ref_src=twsrc%5Etfw">January 15, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>The small team responded impressively quickly, especially given that a <a href="https://www.businessinsider.com/whatsapp-facebook-data-signal-download-telegram-encrypted-messaging-2021-1">4,200% spike</a> in new users was utterly implausible before it occurred.</p>
<p>The downside of so many people moving onto this fantastic application is that it caused a brief outage. If you rely solely on a certain application for your communications, brief outages can be debilitating. Even when it seems implausible that your favorite chat, email, or website service could just &ndash; <em>poof</em> &ndash; vanish overnight, recent events have proved it isn&rsquo;t impossible.</p>
<p>Have a backup plan. Have several. Here&rsquo;s how you can improve your digital resiliency for things like websites, messaging, and email.</p>
<h2 id="messaging">Messaging</h2>
<p>I recommend Signal because it is open source, end-to-end encrypted, cross-platform, and offers text, voice, video, and group chat. It&rsquo;s usually very reliable; however, strange things can happen.</p>
<p>It&rsquo;s important to set up a backup plan ahead of any service outages with the people you communicate with the most. Have an agreement for a secondary method of messaging &ndash; ideally another end-to-end encrypted service. Avoid falling back on insecure communications like SMS and social media messaging. Here&rsquo;s a short list for you to explore:</p>
<ul>
<li><a href="https://signal.org/">Signal</a></li>
<li><a href="https://wire.com/">Wire</a></li>
<li><a href="https://getsession.org/">Session</a></li>
</ul>
<p>If you&rsquo;re particularly technically inclined, you can <a href="/blog/create-a-self-hosted-chat-service-with-your-own-matrix-server/">set up your own self-hosted chat service with Matrix</a>.</p>
<p>Having a go-to plan B can help bring peace of mind and ensure you&rsquo;re still able to communicate when strange things happen.</p>
<h2 id="cloud-contacts">Cloud contacts</h2>
<p>Do you know the phone numbers of your closest contacts? While memorizing them might not be practical, storing them solely online is an unnecessary risk. Most services allow you to export your contacts to vCard or CSV format.</p>
<p>I recommend keeping your contacts locally on your device whenever you can. This ensures you still know how to contact people if your cloud provider is unavailable, or if you don&rsquo;t have Internet access.</p>
<p>Full analog redundancy is also possible here. Remember that paper stuff? Write down the phone numbers of your most important contacts so you can access them if your devices run out of battery or otherwise can&rsquo;t turn on (drop your phone much?).</p>
<h2 id="local-email-synchronization">Local email synchronization</h2>
<p>If your email service exists solely online, there&rsquo;s a big email-shaped hole in your life. If you can&rsquo;t log in to your email for any reason &ndash; an outage on their end, a billing error, or your Internet is down &ndash; you&rsquo;ll have no way to access your messages for however long your exile lasts. If you think about all the things you do via email in a day, I think the appropriate reaction to not having local copies is 🤦.</p>
<p>Download an open source email client like <a href="https://www.thunderbird.net/">Thunderbird</a>. Follow <a href="https://support.mozilla.org/en-US/products/thunderbird/download-install-and-migration">instructions to install Thunderbird</a> and set it up with your existing online email service. Your online service provider may have a help document that shows you how to set up Thunderbird.</p>
<p>You can maximize your privacy by <a href="https://support.mozilla.org/kb/thunderbird-telemetry">turning off Thunderbird&rsquo;s telemetry</a>.</p>
<p>To ensure that Thunderbird downloads your email messages and stores them locally on your machine:</p>
<ol>
<li>Click the &ldquo;hamburger&rdquo; overflow menu and go to <strong>Account Settings</strong></li>
<li>Choose <strong>Synchronization &amp; Storage</strong> in the sidebar</li>
<li>Ensure that under <strong>Message Synchronizing,</strong> the checkbox for <strong>Keep messages in all folders for this account on this computer</strong> is checked.</li>
</ol>
<p>You may need to visit each of your folders in order to trigger the initial download.</p>
<p>Some other settings you may want to update:</p>
<ol>
<li>Choose <strong>Composition &amp; Addressing</strong> and uncheck the box next to <strong>Compose messages in HTML format</strong> to send plaintext emails instead.</li>
<li>Under <strong>Return Receipts</strong> choose <strong>Global Preferences.</strong> Select the radio button for <strong>Never send a return receipt.</strong></li>
</ol>
<p>You don&rsquo;t need to start using Thunderbird for all your email tasks. Just make sure you open it up regularly so that your messages sync and download to your machine.</p>
<h2 id="websites">Websites</h2>
<p>I strongly believe you should have <a href="/posts/make-your-own-independent-website/">your own independent website</a> for reasons that go beyond redundancy. To truly make your site resilient, it&rsquo;s important to have your own domain.</p>
<p>If you know that my website is at the address <code>victoria.dev</code>, for example, it doesn&rsquo;t matter whether I&rsquo;m hosting it on GitHub Pages, AWS, Wordpress, or from a server in my basement. If my hosting provider becomes unavailable, my website won&rsquo;t go down with it. Getting back up and running would be as simple as updating my DNS configuration to point to a new host.</p>
<p>Price is hardly an excuse, either. You can buy a domain for <a href="https://www.jdoqocy.com/click-100268310-14326263" target="_blank" rel="noopener noreferrer">less than a cup of coffee</a>
 with my Namecheap affiliate link (thanks!). Namecheap also handles <a href="https://www.namecheap.com/support/knowledgebase/article.aspx/767/10/how-to-change-dns-for-a-domain/">your DNS settings</a>, so it&rsquo;s a one-stop shop.</p>
<p>With your own domain, you can build resiliency for your email address as well. Learn how to set up your custom domain with your email provider. If you need to switch providers in the future, your email address ports to the new service with you. Here are a few quick links for providers I&rsquo;d recommend:</p>
<ul>
<li><a href="https://proton.me/support/custom-domain">ProtonMail: How to use a custom domain with Proton Mail</a></li>
<li><a href="https://tutanota.com/howto/#custom-domain">Tutanota: Adding of custom email domains</a></li>
<li><a href="https://www.fastmail.help/hc/en-us/articles/360058753394-Custom-Domains-with-Fastmail">Fastmail: Custom Domains with Fastmail</a></li>
</ul>
<h2 id="build-your-digital-resiliency">Build your digital resiliency</h2>
<p>I hope you&rsquo;ve found this article useful on your path to building digital resiliency. If you&rsquo;re interested in more privacy topics, you might like to learn about great <a href="/blog/outsourcing-security-with-1password-authy-and-privacy.com/">apps for outsourcing security</a>.</p>
<p>If your threat model includes anonymity or censorship, building digital resiliency is just a first step. The rest is outside the scope of my blog, but here are a few great resources I&rsquo;ve come across:</p>
<ul>
<li><a href="https://www.torproject.org/">Tor Browser</a></li>
<li><a href="https://inteltechniques.com/index.html">IntelTechniques</a></li>
<li><a href="https://cantcancel.me/">Can&rsquo;t Cancel Me</a></li>
<li><a href="https://tails.boum.org/">Tails portable OS</a></li>
</ul>
]]></content></entry><entry><title type="html">Create a self-hosted chat service with your own Matrix server</title><link href="https://victoria.dev/archive/create-a-self-hosted-chat-service-with-your-own-matrix-server/"/><id>https://victoria.dev/archive/create-a-self-hosted-chat-service-with-your-own-matrix-server/</id><author><name>Victoria Drake</name></author><published>2021-02-15T01:38:07-05:00</published><updated>2021-02-15T01:38:07-05:00</updated><content type="html"><![CDATA[<p><a href="https://matrix.org/docs/guides/introduction">Matrix</a> is an open standard for decentralized real-time communication. The <a href="https://matrix.org/docs/spec/">specification</a> is production-ready and <a href="https://matrix.org/bridges/">bridges</a> to tons of silo products like Slack, Gitter, Telegram, Discord, and even Facebook Messenger. This lets you use Matrix to link together disjoint communities in one place, or create an alternative communication method that works with, but is independent of, communication silos.</p>
<p>You can create your own self-hosted Matrix chat for as little as $3.50 USD per month on an <a href="https://aws.amazon.com/lightsail/">AWS Lightsail</a> instance. Your homeserver can federate with other Matrix servers, giving you a reliable and fault-tolerant means of communication.</p>
<p>Matrix is most widely installed via its <a href="https://element-hq.github.io/synapse/latest/index.html">Synapse</a> homeserver implementation written in Python 3. Dendrite, its second-generation homeserver implementation written in Go, is currently released in beta. Dendrite will provide more memory efficiency and reliability out-of-the-box, making it an excellent choice for running on a virtual instance.</p>
<p>Here&rsquo;s how to set up your own homeserver on AWS Lightsail with Dendrite. You can also <a href="https://github.com/matrix-org/dendrite">contribute to Dendrite today</a>.</p>
<h2 id="create-a-lightsail-instance">Create a Lightsail instance</h2>
<p>Spin up a new Lightsail instance on AWS with Debian as your operating system. It&rsquo;s a good idea to create a new per-instance key for use with SSH. You can do this by with the SSH key pair manager on the instance creation page. Don&rsquo;t forget to download your private key and <code>.gitignore</code> your secrets.</p>
<p>Click <strong>Create Instance.</strong> Wait for the status of your instance to change from <strong>Pending</strong> to <strong>Running</strong>, then click its name to see further information. You&rsquo;ll need the Public IP address.</p>
<p>To enable people including yourself to connect to the instance, go to the Networking tab and add a firewall rule for HTTPS. This will open <code>443</code> so you can connect over IPv4. You can also do this for IPv6.</p>
<h2 id="connect-dns">Connect DNS</h2>
<p>Give your instance a catchier address by <a href="https://www.jdoqocy.com/ds70r09608OQPPRVXSQPOQSRVVVVX" target="_top">buying a domain at Namecheap</a>
 and setting up DNS records.</p>
<ol>
<li>On your domain management page in the <strong>Nameservers</strong> section, choose <strong>Namecheap BasicDNS</strong>.</li>
<li>On the <strong>Advanced DNS</strong> tab, click <strong>Add New Record</strong>.</li>
</ol>
<p>Add an <code>A Record</code> to your Lightsail Public IP. You can use a subdomain if you want one, for example,</p>
<ul>
<li><strong>Type:</strong> <code>A Record</code></li>
<li><strong>Host:</strong> <code>matrix</code></li>
<li><strong>Value:</strong> <code>13.59.251.229</code></li>
</ul>
<p>This points <code>matrix.example.org</code> to your Lightsail instance.</p>
<h2 id="set-up-your-matrix-homeserver">Set up your Matrix homeserver</h2>
<p>Change permissions on the private key you downloaded:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>chmod <span style="color:#ae81ff">600</span> &lt;path/to/key&gt;
</span></span></code></pre></div><p>Then <a href="https://lightsail.aws.amazon.com/ls/docs/en_us/articles/amazon-lightsail-ssh-using-terminal">SSH to your Public IP</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>ssh -i &lt;path/to/key&gt; admin@&lt;public ip&gt;
</span></span></code></pre></div><p>Welcome to your instance! You can make it more interesting by downloading some packages you&rsquo;ll need for Dendrite. It&rsquo;s a good idea to use <code>apt</code> for this, but first you&rsquo;ll want to make sure you&rsquo;re getting the latest stuff.</p>
<p><em>Dec 2021 update: As the good people of Mastodon point out, you might like to ensure you&rsquo;re choosing the stable version for Debian. For instance, replace <code>buster</code> below with <a href="https://www.debian.org/releases/">what&rsquo;s &ldquo;stable&rdquo; at the moment</a>.</em></p>
<p>Change your <a href="https://wiki.debian.org/SourcesList">sources list</a> in order to get the newest version of Go:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>sudo vim /etc/apt/sources.list
</span></span></code></pre></div><p>Delete everything except these two lines:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-vim" data-lang="vim"><span style="display:flex;"><span><span style="color:#a6e22e">deb</span> <span style="color:#a6e22e">http</span>:<span style="color:#e6db74">//</span><span style="color:#a6e22e">cdn</span>-<span style="color:#a6e22e">aws</span>.<span style="color:#a6e22e">deb</span>.<span style="color:#a6e22e">debian</span>.<span style="color:#a6e22e">org</span>/<span style="color:#a6e22e">debian</span> <span style="color:#a6e22e">buster</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">deb</span>-<span style="color:#a6e22e">src</span> <span style="color:#a6e22e">http</span>:<span style="color:#e6db74">//</span><span style="color:#a6e22e">cdn</span>-<span style="color:#a6e22e">aws</span>.<span style="color:#a6e22e">deb</span>.<span style="color:#a6e22e">debian</span>.<span style="color:#a6e22e">org</span>/<span style="color:#a6e22e">debian</span> <span style="color:#a6e22e">buster</span> <span style="color:#a6e22e">main</span>
</span></span></code></pre></div><p>Then replace the distributions:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-vim" data-lang="vim"><span style="display:flex;"><span>:%<span style="color:#a6e22e">s</span><span style="color:#e6db74">/buster main/</span><span style="color:#a6e22e">testing</span> <span style="color:#a6e22e">main</span> <span style="color:#a6e22e">contrib</span> <span style="color:#a6e22e">non</span>-<span style="color:#a6e22e">free</span>/<span style="color:#a6e22e">g</span>
</span></span></code></pre></div><p>Run <code>sudo apt dist-upgrade</code>. If you&rsquo;re asked about modified configuration files, choose the option to &ldquo;keep the local version currently installed.&rdquo;</p>
<p>Once the upgrade is finished, restart your instance with <code>sudo shutdown -r now</code>.</p>
<p>Go make some coffee, then SSH back in. Get the packages you&rsquo;ll need with:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>sudo apt update
</span></span><span style="display:flex;"><span>sudo apt upgrade
</span></span><span style="display:flex;"><span>sudo apt install -y git golang nginx python3-certbot-nginx
</span></span></code></pre></div><p>You&rsquo;re ready to get Dendrite.</p>
<h2 id="get-dendrite">Get Dendrite</h2>
<p>Clone <a href="https://github.com/matrix-org/dendrite">Dendrite</a> and follow the <a href="https://github.com/matrix-org/dendrite#get-started">README instructions to get started</a>. You&rsquo;ll need to choose whether you want your Matrix instance to be federating. For simplicity, here&rsquo;s how to set up a non-federating deployment to start:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>git clone https://github.com/matrix-org/dendrite
</span></span><span style="display:flex;"><span>cd dendrite
</span></span><span style="display:flex;"><span>./build.sh
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Generate a Matrix signing key for federation (required)</span>
</span></span><span style="display:flex;"><span>./bin/generate-keys --private-key matrix_key.pem
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Generate a self-signed certificate (optional, but a valid TLS certificate is normally</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># needed for Matrix federation/clients to work properly!)</span>
</span></span><span style="display:flex;"><span>./bin/generate-keys --tls-cert server.crt --tls-key server.key
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Copy and modify the config file - you&#39;ll need to set a server name and paths to the keys</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># at the very least, along with setting up the database connection strings.</span>
</span></span><span style="display:flex;"><span>cp dendrite-config.yaml dendrite.yaml
</span></span></code></pre></div><h2 id="configure-dendrite">Configure Dendrite</h2>
<p>Modify the configuration file you just copied:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>sudo vim dendrite.yaml
</span></span></code></pre></div><p>At minimum, set:</p>
<ul>
<li><code>server name</code> to your shiny new domain name, e.g. <code>matrix.example.org</code></li>
<li><code>disable_federation</code> to true or false</li>
<li><code>registration_disabled</code> to true or false</li>
</ul>
<p>You might like to read the <a href="https://github.com/matrix-org/dendrite/blob/master/docs/FAQ.md">Dendrite FAQ</a>.</p>
<h2 id="configure-nginx">Configure nginx</h2>
<p>Get the required packages if you didn&rsquo;t already install them above:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>sudo apt install nginx python3-certbot-nginx
</span></span></code></pre></div><p>Create your site&rsquo;s configuration file under <code>sites-available</code> with:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>cd /etc/nginx/sites-available
</span></span><span style="display:flex;"><span>ln -s /etc/nginx/sites-available/&lt;sitename&gt; /etc/nginx/sites-enabled/&lt;sitename&gt;
</span></span><span style="display:flex;"><span>sudo cp default &lt;sitename&gt;
</span></span></code></pre></div><p>Edit your site configuration. Delete the <code>root</code> and <code>index</code> lines if you don&rsquo;t need them, and input your server name.</p>
<p>Your <code>location</code> block should look like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-nginx" data-lang="nginx"><span style="display:flex;"><span><span style="color:#66d9ef">location</span> <span style="color:#e6db74">/</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">proxy_pass</span> <span style="color:#e6db74">https://localhost:8448</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Remove the <code>default</code> with: <code>sudo rm /etc/nginx/sites-enabled/default</code>.</p>
<h2 id="create-self-signed-certificates">Create self-signed certificates</h2>
<p>You can use <a href="https://certbot.eff.org/">Certbot</a> to generate self-signed certificates with <a href="https://letsencrypt.org/">Let&rsquo;s Encrypt</a>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>sudo certbot --nginx -d &lt;your.site.address&gt;
</span></span></code></pre></div><p>If you don&rsquo;t want to give an email, add the <code>--register-unsafely-without-email</code> flag.</p>
<p>Test your configuration and restart nginx with:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>sudo nginx -t
</span></span><span style="display:flex;"><span>sudo systemctl restart nginx
</span></span></code></pre></div><p>Then start up your Matrix server.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span><span style="color:#75715e"># Build and run the server:</span>
</span></span><span style="display:flex;"><span>./bin/dendrite-monolith-server --tls-cert server.crt --tls-key server.key --config dendrite.yaml
</span></span></code></pre></div><p>Your Matrix server is up and running at your web address! If you disabled registration in your configuration, you may need to create a user. You can do this by running the included <code>dendrite/bin/createuser</code>.</p>
<p>You can log on to your new homeserver with any <a href="https://matrix.org/clients/">Matrix client</a>, or Matrix-capable applications like <a href="https://www.pidgin.im/plugins/?publisher=all&amp;query=&amp;type=">Pidgin with the Matrix plugin</a>.</p>
<h2 id="other-troubleshooting">Other troubleshooting</h2>
<h3 id="log-files">Log files</h3>
<p>If you get an error such as:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>... [github.com/matrix-org/dendrite/internal/log.go:155] setupFileHook
</span></span><span style="display:flex;"><span>  Couldn&#39;t create directory /var/log/dendrite: &#34;mkdir /var/log/dendrite: permission denied&#34;
</span></span></code></pre></div><p>You&rsquo;ll need to create a spot for your log files. Avoid the bad practice of running stuff with <code>sudo</code> whenever you can. Instead, create the necessary file with the right permissions:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>sudo mkdir /var/log/dendrite
</span></span><span style="display:flex;"><span>sudo chown admin:admin /var/log/dendrite
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Build and run the server:</span>
</span></span><span style="display:flex;"><span>./bin/dendrite-monolith-server --tls-cert server.crt --tls-key server.key --config dendrite.yaml
</span></span></code></pre></div><h3 id="unable-to-decrypt">Unable to decrypt</h3>
<p>If you see: <code>Unable to decrypt: The sender's device has not sent us the keys for this message.</code> you may need to verify a user (sometimes yourself).</p>
<ol>
<li>In your client, open the user&rsquo;s profile. Click the lock icon if there is one, or otherwise look for a way to verify them.</li>
<li>You may be asked to see if some emojis presented to both users match if you&rsquo;re using certain clients like Element.</li>
<li>You can then re-request encryption keys for any sent messages.</li>
</ol>
<h2 id="set-up-your-own-matrix-server-today">Set up your own Matrix server today</h2>
<p>I hope you found this introduction to setting up your own Matrix homeserver to be helpful!</p>
]]></content></entry><entry><title type="html">Do I Raise or Return Errors in Python?</title><link href="https://victoria.dev/posts/do-i-raise-or-return-errors-in-python/"/><id>https://victoria.dev/posts/do-i-raise-or-return-errors-in-python/</id><author><name>Victoria Drake</name></author><published>2021-02-09T05:34:48-05:00</published><updated>2021-02-09T05:34:48-05:00</updated><content type="html"><![CDATA[<p>I’ve been writing Python for nearly a decade, and this question still comes up in code reviews more often than you’d think. Should I raise an exception or return an error value? It seems simple on the surface, but the choice ripples through your entire codebase in ways that can make or break your team’s productivity six months down the line.</p>
<h2 id="the-real-question-behind-the-question">The Real Question Behind the Question</h2>
<p>When your function discovers something’s wrong, you’re not only choosing between <code>raise</code> and <code>return</code>. You’re making a decision about how your entire application will handle failure, how readable your code will be for the next person, and how many 3 AM prod debugging sessions you’re setting up for your future self and team.</p>
<p>Here&rsquo;s how I think about this choice, because the right one for your application affects everything from your error logs to your team’s velocity.</p>
<h2 id="when-i-reach-for-exceptions">When I Reach for Exceptions</h2>
<p>I raise exceptions when something genuinely unexpected happens—when the assumptions my function was built on just got violated. If I’m writing a function to parse a config file and the file doesn’t exist, that’s exceptional. The caller expected a valid config, and I can’t deliver on that contract.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">load_config</span>(filepath):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#f92672">not</span> os<span style="color:#f92672">.</span>path<span style="color:#f92672">.</span>exists(filepath):
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">raise</span> <span style="color:#a6e22e">FileNotFoundError</span>(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Config file not found: </span><span style="color:#e6db74">{</span>filepath<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">try</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">with</span> open(filepath, <span style="color:#e6db74">&#39;r&#39;</span>) <span style="color:#66d9ef">as</span> f:
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> json<span style="color:#f92672">.</span>load(f)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">except</span> json<span style="color:#f92672">.</span>JSONDecodeError <span style="color:#66d9ef">as</span> e:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">raise</span> ConfigurationError(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Invalid JSON in config file: </span><span style="color:#e6db74">{</span>e<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
</span></span></code></pre></div><p>Here’s why exceptions work well here: the calling code doesn’t need to check every single operation. If any step fails, the exception bubbles up to whoever can actually handle it. Your main application logic stays clean, and error handling happens at the right level.</p>
<p>The business impact here is huge. When your core logic isn’t cluttered with error checking, you can focus on the actual problem you’re solving. Your functions do one thing well, and your error handling is centralized where it belongs.</p>
<h2 id="when-i-return-error-values">When I Return Error Values</h2>
<p>But sometimes the “error” isn’t really an error—it’s just one of several possible outcomes. When I’m building a user search function, finding zero results isn’t exceptional. It’s totally normal behavior that the caller needs to handle anyway.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">search_users</span>(query):
</span></span><span style="display:flex;"><span>    results <span style="color:#f92672">=</span> database<span style="color:#f92672">.</span>search(query)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#f92672">not</span> results:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> []  <span style="color:#75715e"># Empty list, not an exception</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> results
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Calling code feels natural</span>
</span></span><span style="display:flex;"><span>users <span style="color:#f92672">=</span> search_users(<span style="color:#e6db74">&#34;john&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> users:
</span></span><span style="display:flex;"><span>    display_users(users)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>    show_no_results_message()
</span></span></code></pre></div><p>This approach shines when you have multiple valid outcomes and the caller needs to make decisions based on which one occurred. It also works well for performance-critical code where exception handling overhead matters.</p>
<h2 id="the-type-safety-angle">The Type Safety Angle</h2>
<p>Here’s something I’ve started caring about more as codebases grow: how well does your choice play with static type checking? Modern Python with type hints changes the game significantly.</p>
<p>With exceptions, your function signature stays clean:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">parse_user_id</span>(user_input: str) <span style="color:#f92672">-&gt;</span> int:
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">try</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> int(user_input)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">except</span> <span style="color:#a6e22e">ValueError</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">raise</span> InvalidUserIdError(<span style="color:#e6db74">&#34;User ID must be a number&#34;</span>)
</span></span></code></pre></div><p>But with return values, you’re often dealing with unions:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">parse_user_id</span>(user_input: str) <span style="color:#f92672">-&gt;</span> int <span style="color:#f92672">|</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">try</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> int(user_input)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">except</span> <span style="color:#a6e22e">ValueError</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">None</span>
</span></span></code></pre></div><p>That <code>| None</code> propagates through your entire codebase. Every function that calls this one now has to handle the None case, and mypy will remind you of that fact. Sometimes that’s exactly what you want—explicit error handling at every level. Other times, it creates unnecessary complexity.</p>
<h2 id="the-performance-reality-check">The Performance Reality Check</h2>
<p>What about performance? Yes, exceptions are slower than returning values, but context matters enormously here.</p>
<p>In tight loops processing thousands of items per second, that overhead can add up. Profiling code where you&rsquo;ve switched from exceptions to return values might show improved performance of 20-30%. But in typical web application code where you’re dealing with database calls and network requests, exception overhead is noise compared to everything else.</p>
<p>The more important performance consideration is often developer performance. How quickly can someone understand your code? How easily can they modify it without introducing bugs? I’ve seen teams spend weeks debugging subtle issues that wouldn’t have existed with clearer (documented!) error handling patterns.</p>
<h2 id="patterns-that-actually-work-in-production">Patterns That Actually Work in Production</h2>
<p>After working on systems that handle millions of requests, here are the patterns I keep coming back to:</p>
<p><strong>For library code</strong>: Raise exceptions. Libraries don’t know how their callers want to handle errors, so push that decision up the stack. Custom exception types help callers decide what to catch and what to let bubble up.</p>
<p><strong>For user input validation</strong>: Usually return structured error information. Users make mistakes constantly, and that’s normal behavior, not exceptional.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">validate_email</span>(email: str) <span style="color:#f92672">-&gt;</span> ValidationResult:
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#f92672">not</span> email:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> ValidationResult(valid<span style="color:#f92672">=</span><span style="color:#66d9ef">False</span>, error<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Email is required&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#e6db74">&#34;@&#34;</span> <span style="color:#f92672">not</span> <span style="color:#f92672">in</span> email:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> ValidationResult(valid<span style="color:#f92672">=</span><span style="color:#66d9ef">False</span>, error<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Invalid email format&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> ValidationResult(valid<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span></code></pre></div><p><strong>For external service calls</strong>: This is tricky. Network timeouts and service errors happen, but they’re not exactly exceptional in a distributed system. I often use exceptions for the truly unexpected (DNS resolution failures) and return values for the predictable failures (rate limiting, temporary service unavailability).</p>
<h2 id="the-3am-system-down-test">The 3AM System Down Test</h2>
<p>Here’s my ultimate thought experiment test: if something broke in production and you had to debug it at 3 AM, bleary-eyed and chugging coffee, which approach helps you understand what went wrong faster?</p>
<p>Good exceptions with detailed error messages and proper stack traces are incredible for this. You can see exactly where things went wrong and why. But exceptions that get swallowed or re-raised without context are debugging nightmares.</p>
<p>Return values with proper logging can also be great for debugging, especially when you need to understand the sequence of events that led to a problem. But they require more discipline—you need to actually check and log those return values.</p>
<h2 id="making-the-choice">Making the Choice</h2>
<p>When I’m looking at a specific function, I ask myself:</p>
<ul>
<li>Is this condition truly unexpected given the function’s contract?</li>
<li>Do callers need to make immediate decisions based on this failure?</li>
<li>How will this pattern scale across my team and codebase?</li>
<li>What will debugging look like when this inevitably breaks?</li>
</ul>
<p>There’s no universal right answer, but there are patterns that work well for different situations. The key is being intentional about your choice and consistent within your codebase.</p>
<p>Your error handling strategy affects how quickly new team members can contribute, how easy it is to track down production issues, and how confident you can be when making changes. Choose patterns that serve your team’s long-term productivity, not just today’s immediate problem.</p>
<p>The best choice for error handling is the one that helps you sleep better at night, knowing that when something goes wrong, you’ll be able to figure out what happened and fix it quickly.</p>
<p>If you found some value in this post, there&rsquo;s more! I write about high-output development processes and building maintainable systems in the AI age. You can get my posts in your inbox by subscribing below.</p>
<a href="https://medium.com/@victoriadotdev/subscribe" target="_blank" rel="noopener noreferrer" class="subscribe-button">
    <span class="subscribe-icon">📧</span>
    <span class="subscribe-text">Subscribe</span>
</a>

]]></content></entry><entry><title type="html">What Tech Leaders Do Before Going on Vacation</title><link href="https://victoria.dev/posts/what-tech-leaders-do-before-going-on-vacation/"/><id>https://victoria.dev/posts/what-tech-leaders-do-before-going-on-vacation/</id><author><name>Victoria Drake</name></author><published>2021-02-01T04:02:54-06:00</published><updated>2021-02-01T04:02:54-06:00</updated><content type="html"><![CDATA[<p>Early in my career, I worked on a team where the CEO decided to take two weeks off without much preparation. By the middle of the first week, people had “run out” of things to do. Not because there wasn’t work—there was plenty—but because no one knew what they were supposed to prioritize, who could make decisions, or how to move forward on anything that required input from leadership.</p>
<p>We spent those two weeks in a weird organizational limbo, working on whatever seemed important while bigger decisions piled up. Upon returning, the CEO was frustrated that so little had been accomplished, and the team was frustrated that they’d been left without clear direction. It was a perfect example of how taking time off as a leader requires completely different preparation than taking time off as an individual contributor.</p>
<p>The reality is that leadership vacation planning isn’t about finishing your own work—it’s about ensuring your team can function effectively without you. Done well, it’s actually a powerful way to develop your team’s autonomy and decision-making capabilities. Done poorly, it creates exactly the kind of organizational dysfunction I witnessed firsthand.</p>
<h2 id="the-information-bottleneck-problem">The Information Bottleneck Problem</h2>
<p>Here’s what most leaders don’t realize: you’re probably a bigger bottleneck than you think. Not because you’re micromanaging, but because critical context lives in your head that your team needs access to in order to make good decisions. The challenge isn’t documenting everything you know—that’s impossible. The challenge is identifying what your team will actually need while you’re gone.</p>
<p>I’ve learned to approach this systematically. Instead of trying to dump all my knowledge, I focus on the specific work my team will be doing during my absence. What decisions might come up that I can provide context for? What blockers could they encounter and who could help in my absence? Who will take the lead on making decisions to help keep projects moving forward?</p>
<p>This exercise often reveals gaps in team communication that extend beyond vacation planning.</p>
<blockquote>
<p>If people don’t know how to prioritize work when you’re gone for a week, they probably struggle with prioritization day-to-day more than you realize.</p>
</blockquote>
<p>Vacation prep becomes a forcing function for better ongoing delegation.</p>
<p>The practical approach is straightforward: review your priority list and write down the context and contacts that your team will need to get work done while you&rsquo;re away. But the deeper value is discovering where your team needs more autonomy and decision-making authority in general.</p>
<h2 id="decision-making-without-you">Decision-Making Without You</h2>
<p>The most common mistake I see leaders make is trying to pre-decide everything that might come up while they’re away. This is both impossible and counterproductive. Instead, the goal should be empowering your team to make good decisions using the same framework you would use.</p>
<p>Before any significant time off, I have explicit conversations with my team about what kinds of decisions they can make independently and what should wait for my return. More importantly, I explain the reasoning behind those boundaries so they understand when to escalate and when to proceed.</p>
<p>More than just being on the same page, these boundaries help to build your team’s confidence in their own judgment.</p>
<blockquote>
<p>When people understand your decision-making criteria and feel trusted to apply them, they’ll make better choices whether you’re away on vacation or away in a meeting.</p>
</blockquote>
<p>The key is being specific about decision authority rather than vague about “checking with me first.” Instead of saying “let me know if anything important comes up,” try “you can approve any engineering changes that don’t affect the database schema, but flag anything that requires downtime for discussion when I’m back.”</p>
<h2 id="creating-clarity-not-chaos">Creating Clarity, Not Chaos</h2>
<p>The difference between teams that thrive when their leader is away and teams that stagnate comes down to clarity of expectations. Your team needs to know not just what to work on, but how to make trade-offs when priorities conflict, who to go to for different types of help, and what success looks like in your absence.</p>
<p>I’ve found that internal communication about your time off is just as important as external auto-responders.</p>
<blockquote>
<p>A quick message to your team explaining where to find information, who’s covering what responsibilities, and how to handle common scenarios prevents a lot of confusion and hesitation.</p>
</blockquote>
<p>But the real test is whether your team feels empowered to act or feels like they’re in caretaker mode until you return. The goal is maintaining momentum, not just maintaining the status quo. This requires trusting your team with meaningful work and giving them the context they need to handle unexpected situations.</p>
<h2 id="the-leadership-development-opportunity">The Leadership Development Opportunity</h2>
<p>Your vacation is actually a development opportunity for your team if you set it up intentionally.</p>
<blockquote>
<p>When you step back temporarily, you create space for other people to step up, make decisions, and take on leadership responsibilities.</p>
</blockquote>
<p>Instead of just hoping things will be fine while you’re gone, use your absence as a chance to test and develop your team’s capabilities. Give someone the opportunity to run meetings, handle stakeholder communication, or make technical decisions that they’re ready for but haven’t had the chance to practice.</p>
<p>The preparation for this kind of delegation is more involved than just finishing your own work, but the payoff is enormous. You return to a team that’s more capable and confident, and you’ve identified who’s ready for additional responsibilities. Plus, you’ve stress-tested your team’s ability to function without you, which is valuable information for organizational resilience.</p>
<h2 id="making-time-off-actually-restful">Making Time Off Actually Restful</h2>
<p>The irony of leadership is that taking time off can be stressful if you’re worried about what’s happening while you’re away. The best vacation preparation eliminates that anxiety by ensuring your team has everything they need to succeed without you.</p>
<p>This means being honest about your availability expectations and sticking to them. If you tell your team you’ll be completely offline, don’t check Slack “just once” and end up getting pulled into work discussions. If you’re going to check in periodically, be specific about when and how, so people know what to expect.</p>
<p>The teams that handle leadership time off best are the ones where this kind of preparation is routine, not exceptional. When delegation, clear communication, and decision-making authority are part of your regular management practice, preparing for vacation becomes straightforward rather than stressful.</p>
<p>Your time off should leave your team more capable, not less. When you return from vacation to find that your team tackled challenges, made good decisions, and maintained momentum without you, you’ll know you’ve built something sustainable. That’s not just good vacation planning—it’s good leadership.</p>
]]></content></entry><entry><title type="html">Add search to Hugo static sites with Lunr</title><link href="https://victoria.dev/archive/add-search-to-hugo-static-sites-with-lunr/"/><id>https://victoria.dev/archive/add-search-to-hugo-static-sites-with-lunr/</id><author><name>Victoria Drake</name></author><published>2021-01-26T09:25:17-05:00</published><updated>2021-01-26T09:25:17-05:00</updated><content type="html"><![CDATA[<p>Yes, you <em>can</em> have an interactive search feature on your static site! No need for servers or paid subscriptions here. Thanks to the open source <a href="https://lunrjs.com/">Lunr</a> and the power of <a href="https://gohugo.io/">Hugo static site generator</a>, you can create a client-side search index with just a template and some JavaScript.</p>
<p>A number of my readers have been kind enough to tell me that you find my blog useful, but there&rsquo;s something that you don&rsquo;t know. Up until I recently implemented a search feature on <a href="/">victoria.dev</a>, I had been my own unhappiest user.</p>
<p>My blog exists for all to read, but it&rsquo;s also my own personal Internet brain. I frequently pull up a post I&rsquo;ve written when trying to re-discover some bit of knowledge that I may have had the foresight to record. Without a search, finding it again took a few clicks and more than a few guesses. Now, all my previous discoveries are conveniently at my fingertips, ready to be rolled into even more future work.</p>
<p>If you&rsquo;d like to make your own personal Internet brain more useful, here&rsquo;s how you can implement your own search feature on your static Hugo site.</p>
<h2 id="get-lunr">Get Lunr</h2>
<p>While you can <a href="https://lunrjs.com/guides/getting_started.html">install lunr.js</a> via npm or include it from a CDN, I chose to vendorize it to minimize network impact. This means I host it from my own site files by placing the library in Hugo&rsquo;s <code>static</code> directory.</p>
<p>You can save your visitors some bandwidth by minifying <code>lunr.js</code>, which I did just by <a href="https://github.com/olivernn/lunr.js">downloading lunr.js from source</a> and using the <a href="https://github.com/olback/es6-css-minify">JS &amp; CSS Minifier Visual Studio Code extension</a> on the file. That brought the size down roughly 60% from 97.5 KB to 39.35 KB.</p>
<p>Save this as <code>static/js/lunr.min.js</code>.</p>
<h2 id="create-a-search-form-partial">Create a search form partial</h2>
<p>To easily place your search form wherever you like on your site, create the form as a partial template at <code>layouts/partials/search-form.html</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#f92672">form</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;search&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">action</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#39;{{ with .GetPage &#34;/search&#34; }}{{.Permalink}}{{end}}&#39;</span> <span style="color:#a6e22e">method</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;get&#34;</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">label</span> <span style="color:#a6e22e">hidden</span> <span style="color:#a6e22e">for</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;search-input&#34;</span>&gt;Search site&lt;/<span style="color:#f92672">label</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">input</span> <span style="color:#a6e22e">type</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;text&#34;</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;search-input&#34;</span> <span style="color:#a6e22e">name</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;query&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">placeholder</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Type here to search&#34;</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">input</span> <span style="color:#a6e22e">type</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;submit&#34;</span> <span style="color:#a6e22e">value</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;search&#34;</span>&gt;
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">form</span>&gt;
</span></span></code></pre></div><p>Include your search form in other templates with:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>{{ partial &#34;search-form.html&#34; . }}
</span></span></code></pre></div><h2 id="create-a-search-page">Create a search page</h2>
<p>For your search to be useful, you&rsquo;ll need a way to trigger one. You can create a (static!) <code>/search</code> page that responds to a GET request, runs your search, and displays results.</p>
<p>Here&rsquo;s how to create a Hugo template file for a search page and get it to render.</p>
<p>Create <code>layouts/search/list.html</code> with the following minimum markup, assuming you&rsquo;re inheriting from a base template:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>{{ define &#34;main&#34; }}
</span></span><span style="display:flex;"><span>{{ partial &#34;search-form.html&#34; . }}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>&lt;<span style="color:#f92672">ul</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;results&#34;</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">li</span>&gt;
</span></span><span style="display:flex;"><span>        Enter a keyword above to search this site.
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">li</span>&gt;
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">ul</span>&gt;
</span></span><span style="display:flex;"><span>{{ end }}
</span></span></code></pre></div><p>In order to get Hugo to render the template, a matching content file must be available. Create <code>content/search/_index.md</code> to satisfy this requirement. The file just needs minimal <a href="https://gohugo.io/content-management/front-matter">front matter</a> to render:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-md" data-lang="md"><span style="display:flex;"><span>---
</span></span><span style="display:flex;"><span>title: Search me!
</span></span><span style="display:flex;"><span>---
</span></span></code></pre></div><p>You can run <code>hugo serve</code> and navigate to <code>/search</code> to see if everything builds as expected.</p>
<p>A few libraries exist to help you build a search index and implement Lunr. You can find them <a href="https://gohugo.io/tools/search/">here on the Hugo site</a>. If you want to fully understand the process, however, you&rsquo;ll find it&rsquo;s not complicated do this without additional dependencies, thanks to the power of Hugo&rsquo;s static site processing.</p>
<h2 id="build-your-search-index">Build your search index</h2>
<p>Here&rsquo;s how to build an index for Lunr to search using Hugo&rsquo;s template rendering power. Use <code>range</code> to loop over the pages you want to make searchable, and capture your desired parameters in an array of documents. One way to do this is to create <code>layouts/partials/search-index.html</code> with:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#f92672">script</span>&gt;
</span></span><span style="display:flex;"><span>window.<span style="color:#a6e22e">store</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// You can specify your blog section only:
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    {{ <span style="color:#a6e22e">range</span> <span style="color:#a6e22e">where</span> .<span style="color:#a6e22e">Site</span>.<span style="color:#a6e22e">Pages</span> <span style="color:#e6db74">&#34;Section&#34;</span> <span style="color:#e6db74">&#34;posts&#34;</span> }}
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// For all pages in your site, use &#34;range .Site.Pages&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#75715e">// You can use any unique identifier here
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#e6db74">&#34;{{ .Permalink }}&#34;</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// You can customize your searchable fields using any .Page parameters
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#e6db74">&#34;title&#34;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;{{ .Title  }}&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;tags&#34;</span><span style="color:#f92672">:</span> [{{ <span style="color:#a6e22e">range</span> .<span style="color:#a6e22e">Params</span>.<span style="color:#a6e22e">Tags</span> }}<span style="color:#e6db74">&#34;{{ . }}&#34;</span>,{{ <span style="color:#a6e22e">end</span> }}],
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;content&#34;</span><span style="color:#f92672">:</span> {{ .<span style="color:#a6e22e">Content</span> <span style="color:#f92672">|</span> <span style="color:#a6e22e">plainify</span> }}, <span style="color:#75715e">// Strip out HTML tags
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#e6db74">&#34;url&#34;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;{{ .Permalink }}&#34;</span>
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    {{ <span style="color:#a6e22e">end</span> }}
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">script</span>&gt;
</span></span><span style="display:flex;"><span><span style="color:#75715e">&lt;!-- Include Lunr and code for your search function,
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">which you&#39;ll write in the next section --&gt;</span>
</span></span><span style="display:flex;"><span>&lt;<span style="color:#f92672">script</span> <span style="color:#a6e22e">src</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;/js/lunr.min.js&#34;</span>&gt;&lt;/<span style="color:#f92672">script</span>&gt;
</span></span><span style="display:flex;"><span>&lt;<span style="color:#f92672">script</span> <span style="color:#a6e22e">src</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;/js/search.js&#34;</span>&gt;&lt;/<span style="color:#f92672">script</span>&gt;
</span></span></code></pre></div><p>When Hugo renders your site, it will build your search index in much the same way as <a href="https://gohugo.io/templates/lists#what-is-a-list-page-template">a List page</a> is built, creating a document for each page with its parameters.</p>
<p>The last piece of the puzzle is the code to handle the search process: taking the search query, getting Lunr to perform the search, and displaying the results.</p>
<h2 id="perform-the-search-and-show-results">Perform the search and show results</h2>
<p>Create <code>static/js/search.js</code> to hold the JavaScript that ties it all together. This file has three main tasks: get the search query, perform the search with Lunr, and display the results.</p>
<h3 id="get-query-parameters-with-javascript">Get query parameters with JavaScript</h3>
<p>This part&rsquo;s straightforward thanks to <a href="https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams">URLSearchParams</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">params</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">URLSearchParams</span>(window.<span style="color:#a6e22e">location</span>.<span style="color:#a6e22e">search</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">query</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">params</span>.<span style="color:#a6e22e">get</span>(<span style="color:#e6db74">&#39;q&#39;</span>)
</span></span></code></pre></div><h3 id="search-for-the-query-with-lunr">Search for the query with Lunr</h3>
<p>Define and configure an <a href="https://lunrjs.com/guides/getting_started.html#creating-an-index">index for Lunr</a>. This tells Lunr what you&rsquo;d like to search with, and you can optionally <code>boost</code> elements that are more important.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">idx</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">lunr</span>(<span style="color:#66d9ef">function</span> () {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Search these fields
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">this</span>.<span style="color:#a6e22e">ref</span>(<span style="color:#e6db74">&#39;id&#39;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">this</span>.<span style="color:#a6e22e">field</span>(<span style="color:#e6db74">&#39;title&#39;</span>, {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">boost</span><span style="color:#f92672">:</span> <span style="color:#ae81ff">15</span>
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">this</span>.<span style="color:#a6e22e">field</span>(<span style="color:#e6db74">&#39;tags&#39;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">this</span>.<span style="color:#a6e22e">field</span>(<span style="color:#e6db74">&#39;content&#39;</span>, {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">boost</span><span style="color:#f92672">:</span> <span style="color:#ae81ff">10</span>
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Add the documents from your search index to
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#75715e">// provide the data to idx
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">for</span> (<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">key</span> <span style="color:#66d9ef">in</span> window.<span style="color:#a6e22e">store</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">this</span>.<span style="color:#a6e22e">add</span>({
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">id</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">key</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">title</span><span style="color:#f92672">:</span> window.<span style="color:#a6e22e">store</span>[<span style="color:#a6e22e">key</span>].<span style="color:#a6e22e">title</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">tags</span><span style="color:#f92672">:</span> window.<span style="color:#a6e22e">store</span>[<span style="color:#a6e22e">key</span>].<span style="color:#a6e22e">category</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">content</span><span style="color:#f92672">:</span> window.<span style="color:#a6e22e">store</span>[<span style="color:#a6e22e">key</span>].<span style="color:#a6e22e">content</span>
</span></span><span style="display:flex;"><span>        })
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>})
</span></span></code></pre></div><p>You can then execute the search and store results with:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">results</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">idx</span>.<span style="color:#a6e22e">search</span>(<span style="color:#a6e22e">query</span>)
</span></span></code></pre></div><h3 id="display-results">Display results</h3>
<p>You&rsquo;ll need a function that builds a list of results and displays them on your search page. Recall the <code>id</code> you gave your <code>ul</code> element in <code>layouts/search/list.html</code> and store it as a variable:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">searchResults</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">getElementById</span>(<span style="color:#e6db74">&#39;results&#39;</span>)
</span></span></code></pre></div><p>If a search results in some results (🥁), you can iterate over them and build a <code>&lt;li&gt;</code> element for each one.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">results</span>.<span style="color:#a6e22e">length</span>) { <span style="color:#75715e">// Length greater than 0 is truthy
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">resultList</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> (<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">n</span> <span style="color:#66d9ef">in</span> <span style="color:#a6e22e">results</span>) {
</span></span><span style="display:flex;"><span>      <span style="color:#75715e">// Use the unique ref from the results list to get the full item
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>      <span style="color:#75715e">// so you can build its &lt;li&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>      <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">item</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">store</span>[<span style="color:#a6e22e">results</span>[<span style="color:#a6e22e">n</span>].<span style="color:#a6e22e">ref</span>]
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">resultList</span> <span style="color:#f92672">+=</span> <span style="color:#e6db74">&#39;&lt;li&gt;&lt;p&gt;&lt;a href=&#34;&#39;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">item</span>.<span style="color:#a6e22e">url</span> <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39;&#34;&gt;&#39;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">item</span>.<span style="color:#a6e22e">title</span> <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39;&lt;/a&gt;&lt;/p&gt;&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#75715e">// Add a short clip of the content
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>      <span style="color:#a6e22e">resultList</span> <span style="color:#f92672">+=</span> <span style="color:#e6db74">&#39;&lt;p&gt;&#39;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">item</span>.<span style="color:#a6e22e">content</span>.<span style="color:#a6e22e">substring</span>(<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">150</span>) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39;...&lt;/p&gt;&lt;/li&gt;&#39;</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">searchResults</span>.<span style="color:#a6e22e">innerHTML</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">resultList</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>For each of your results, this produces a list item similar to:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#f92672">li</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">p</span>&gt;
</span></span><span style="display:flex;"><span>        &lt;<span style="color:#f92672">a</span> <span style="color:#a6e22e">href</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;.../blog/add-search-to-hugo-with-lunr/&#34;</span>&gt;
</span></span><span style="display:flex;"><span>        Add search to Hugo static sites with Lunr
</span></span><span style="display:flex;"><span>        &lt;/<span style="color:#f92672">a</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">p</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">p</span>&gt;Yes, you can have an interactive search feature on your static site!...&lt;/<span style="color:#f92672">p</span>&gt;
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">li</span>&gt;
</span></span></code></pre></div><p>If there are no results, ham-handedly insert a message instead.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">searchResults</span>.<span style="color:#a6e22e">innerHTML</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;No results found.&#39;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="full-code-for-searchjs">Full code for search.js</h3>
<p>Here&rsquo;s what <code>static/js/search.js</code> could look like in full.</p>
<details>
<summary>search.js full code</summary>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"></code></pre></div></details>
<h2 id="make-it-go">Make it go</h2>
<p>You now have Lunr, the search index, and the code that displays results. Since these are all included in <code>layouts/partials/search-index.html</code>, add this partial on all pages with a search form. In your page footer, place:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>{{ partial &#34;search-index.html&#34; . }}
</span></span></code></pre></div><p>You can see what this looks like when it&rsquo;s all put together by trying it out <a href="/blog">on my blog</a>.</p>
<h2 id="make-it-go-faster">Make it go faster</h2>
<p>Since your site is static, it&rsquo;s possible to <a href="https://lunrjs.com/guides/index_prebuilding.html">pre-build your search index</a> as a JSON data file for Lunr to load. This is where those <a href="https://gohugo.io/tools/search/">aforementioned libraries</a> may be helpful, since a JSON-formatted search index would need to be built outside of running <code>hugo</code> to generate your site.</p>
<p>You can maximize your search speed by minifying assets, and minimizing computationally expensive or blocking JavaScript in your code.</p>
<h2 id="static-sites-get-search-too">Static sites get search, too!</h2>
<p>I hope this helps you make your Internet brain more useful for yourself and others, too! Don&rsquo;t worry if you haven&rsquo;t got the time to implement a search feature today &ndash; you can find this tutorial again when you visit <a href="/blog">victoria.dev</a> and search for this post! 🥁</p>
]]></content></entry><entry><title type="html">Make your own independent website</title><link href="https://victoria.dev/archive/make-your-own-independent-website/"/><id>https://victoria.dev/archive/make-your-own-independent-website/</id><author><name>Victoria Drake</name></author><published>2021-01-16T08:41:27-05:00</published><updated>2021-01-16T08:41:27-05:00</updated><content type="html"><![CDATA[<p>The web that raised me was a digital playground in the truest sense. It was made up of HTML experiments Frankensteined together by people still figuring it all out.</p>
<p>The beauty of not completely knowing what you&rsquo;re doing is a lack of premature judgement. Without a standard to rise to, you&rsquo;re free to go sideways. Explore. Try things that don&rsquo;t work, without any expectation they <em>will</em> work. An open world with a beginner&rsquo;s mindset.</p>
<p>The web that raised me was a little broken. Things didn&rsquo;t always display the way they were supposed to. That too is part of the beauty. It was just broken enough to make you think for yourself.</p>
<p>1991 was the year of the individual on the web, the <a href="https://en.wikipedia.org/wiki/History_of_the_World_Wide_Web" target="_blank" rel="noopener noreferrer">first year</a>
 any layperson could open a web browser and access the new hypermedia dimension. There were no go-to, search-suggested, centralized websites. There were newsgroups. You had what you made and what your meatspace contacts sent you. In 2021, I think we need a return to that level of individualism. We need to make 2021 the year of the independent web.</p>
<p>That&rsquo;s not to say I think the massive monopolistic platforms are going anywhere. Twitter, Facebook, mainstream &ldquo;news&rdquo; media sites &ndash; they&rsquo;re all a kind of utility now, like plumbing and electricity. They&rsquo;ll find their place in regulation and history. But they are not <em>your website.</em></p>
<p>Your website is the one you create. Where the content, top-to-bottom, is yours alone to shape and present as you please. Your website is your place of self-expression, without follower counts or statistics to game. Your website is for creation, not reaction.</p>
<p>It&rsquo;s all yours, but it doesn&rsquo;t have to seem lonely. Your site can interact with the entire online world through syndication and protocols made possible by this thing we call the Internet. See:</p>
<ul>
<li><a href="https://indieweb.org/POSSE" target="_blank" rel="noopener noreferrer">IndieWeb</a>
 for POSSE, an abbreviation for Publish (on your) Own Site, Syndicate Elsewhere</li>
<li><a href="https://www.w3.org/TR/2017/REC-webmention-20170112/" target="_blank" rel="noopener noreferrer">Webmention</a>
 and <a href="https://github.com/aaronpk/webmention.io" target="_blank" rel="noopener noreferrer">an easy way to implement them</a>
</li>
<li><a href="https://github.com/buckket/twtxt" target="_blank" rel="noopener noreferrer">twtxt</a>
 instances for a decentralized timeline experience</li>
<li><a href="https://neofeed.dev/" target="_blank" rel="noopener noreferrer">Neofeed</a>
, my personal timeline project made for <a href="https://neocities.org/" target="_blank" rel="noopener noreferrer">Neocities</a>
. (It&rsquo;s open source and <a href="https://github.com/victoriadrake/neocities-neofeed" target="_blank" rel="noopener noreferrer">you can help me extend it!</a>
)</li>
</ul>
<p>Your website is your beginning point. The one source of truth for your identity online, from which you can generate and distribute disposable copies to any platform you please. This is what it means to truly own your content. And on the Internet, your content is you.</p>
<p>This is my website. When I first created it, I did so for myself. I had no expectation of visitors. I just knew I&rsquo;d rather have these thoughts and things I&rsquo;ve learned here, out here, made indelible in the folds of the public Internet, instead of on some dark corner of my machine, to be lost forever once I am.</p>
<p>Make your own website. You&rsquo;ll grow your own sense of well-deserved accomplishment and contribute to your independence on the web. You&rsquo;ll learn by doing, by scratching your own itch.</p>
<p>Learn about web technologies. Use them as you would if you were a child holding a pencil or paintbrush for the first time. Experiment, with no expectations other than discovering what you can do to make it delight you.</p>
<hr>
<p>These sites and articles inspired this post and helped me implement webmentions!</p>
<ul>
<li><a href="https://dangillmor.com/2014/04/25/indie-web-important/" target="_blank" rel="noopener noreferrer">Why the Indie Web movement is so important, Dan Gillmor</a>
</li>
<li><a href="https://www.jvt.me/" target="_blank" rel="noopener noreferrer">Jamie Tanna</a>
</li>
<li><a href="https://mxb.dev/blog/" target="_blank" rel="noopener noreferrer">Max Böck</a>
</li>
<li><a href="https://paulrobertlloyd.com/" target="_blank" rel="noopener noreferrer">Paul Robert Lloyd</a>
</li>
<li><a href="https://adhoc.systems/notes" target="_blank" rel="noopener noreferrer">Zachary Dunn</a>
</li>
<li><a href="https://anaulin.org/blog/adding-webmentions/" target="_blank" rel="noopener noreferrer">Adding Webmentions to My Static Hugo Site, Ana Ulin</a>
</li>
<li><a href="https://keithjgrant.com/posts/2019/02/adding-webmention-support-to-a-static-site/" target="_blank" rel="noopener noreferrer">Adding Webmention Support to a Static Site, Keith J Grant</a>
</li>
<li><a href="https://webmention.rocks/" target="_blank" rel="noopener noreferrer">Webmention.rocks</a>
</li>
</ul>
]]></content></entry><entry><title type="html">How to Choose a Great Tech Hire</title><link href="https://victoria.dev/posts/how-to-choose-a-great-tech-hire/"/><id>https://victoria.dev/posts/how-to-choose-a-great-tech-hire/</id><author><name>Victoria Drake</name></author><published>2021-01-12T05:50:53-05:00</published><updated>2021-01-12T05:50:53-05:00</updated><content type="html"><![CDATA[<p>I’ve seen too many hiring processes that focus on the wrong things. Teams spend hours on algorithm puzzles and whiteboard exercises, then hire someone who can’t write readable code or collaborate effectively with colleagues. Six months later, they’re dealing with either a performance issue or an unexpected resignation from someone who never felt like they fit the team.</p>
<p>These candidates don&rsquo;t lack technical ability. The problem is that traditional hiring processes don’t predict who will actually succeed and stay on your team. After years of hiring engineers and watching some thrive while others struggle, I’ve learned that the best predictors of long-term success are often the things most interviews completely miss.</p>
<p>Here’s what I actually look for when hiring engineers, and why these signals matter more than most technical assessments.</p>
<h2 id="look-for-builders-not-just-coders">Look for Builders, Not Just Coders</h2>
<p>The question that matters most isn’t “Can they solve algorithm problems?” It’s “Can they build things that solve problems?” There’s a fundamental difference between someone who can write code and someone who can deliver working software that serves a purpose.</p>
<p>When I review candidates, I’m looking for evidence that they’ve built complete projects from start to finish. Not just coding exercises or tutorial follow-alongs, but actual working software that solves real problems. This could be command-line utilities, web applications, automation tools, or contributions to open source projects—the complexity matters less than the completeness.</p>
<p>What I’m really evaluating is their ability to navigate the full software development lifecycle. Can they scope a problem, make technical decisions, handle edge cases, write documentation, and ship something that actually works? These are the skills that translate directly to success on your team, regardless of whether they learned them in a computer science program or taught themselves on weekends.</p>
<p>The best candidates can walk you through their projects and explain not just how they built something, but why they made specific technical choices. They understand the trade-offs they made and can articulate what they learned from the experience. This kind of thinking is what distinguishes engineers who will contribute meaningfully to your team from those who will struggle to move beyond assigned tasks.</p>
<h2 id="evaluate-systems-thinking-over-syntax-knowledge">Evaluate Systems Thinking Over Syntax Knowledge</h2>
<p>Most technical interviews focus on whether someone knows specific syntax or can solve isolated problems. But the engineers who succeed on teams are the ones who understand how their code fits into larger systems and affects other people’s work.</p>
<p>I look for candidates who demonstrate awareness of follow-on effects. When they describe a project, do they consider performance implications? Do they think about maintainability? Can they explain how their technical decisions might impact other developers or users?</p>
<p>Understanding concepts like mutability, thread safety, and code reusability shows technical competence as well as thinking systematically about software as something that exists in a larger context. Engineers who grasp these concepts naturally write code that’s easier to debug, extend, and maintain. They consider the total cost of ownership, not just the immediate implementation.</p>
<p>During interviews, I ask candidates to explain technical trade-offs they’ve made in their projects. The specific technologies matter less than their ability to reason about complexity, performance, and maintainability. Engineers who think this way will continue learning and adapting as your company&rsquo;s tech stack evolves.</p>
<h2 id="assess-communication-skills-through-real-examples">Assess Communication Skills Through Real Examples</h2>
<p>Communication skills aren’t just a “nice to have” for engineers—they’re essential for team effectiveness. But most hiring processes assess communication through artificial interview scenarios rather than looking at how candidates actually communicate about technical topics.</p>
<p>I spend significant time reviewing candidates’ written communication. How do they explain their projects in README files? How do they participate in open source discussions? Can they write clear, helpful documentation? These examples reveal how they’ll communicate with your team when explaining technical decisions, documenting systems, or participating in code reviews.</p>
<p>Pay attention to how candidates describe complex technical concepts during interviews. Can they adjust their explanation based on their audience’s technical background? Do they provide context and examples? Can they acknowledge when they don’t know something without becoming defensive?</p>
<p>The engineers who succeed long-term are those who can collaborate effectively across different skill levels and backgrounds. They can explain technical concepts to non-technical stakeholders, provide helpful code review feedback, and contribute to architectural discussions. These collaborative skills are often better predictors of success than pure technical ability.</p>
<h2 id="identify-team-players-through-contribution-patterns">Identify Team Players Through Contribution Patterns</h2>
<p>The best predictor of how someone will behave on your team is how they’ve behaved on other teams. Rather than asking hypothetical questions about teamwork, look at concrete examples of how candidates have collaborated with others.</p>
<p>Open source contributions provide excellent insight into someone’s collaborative style. How do they handle feedback on their code? Do they contribute thoughtfully to discussions? Can they work within existing conventions and standards? Do they help other contributors or just focus on their own work?</p>
<p>For candidates without extensive open source history, look at how they talk about past team experiences. Do they credit others for successes? Can they describe situations where they helped colleagues or learned from feedback? How do they handle disagreement or conflict?</p>
<p>I’m particularly interested in candidates who show evidence of helping others grow. Engineers who mentor junior developers, contribute to team documentation, or improve development processes tend to have a positive impact that extends far beyond their individual contributions.</p>
<h2 id="evaluate-learning-ability-over-current-knowledge">Evaluate Learning Ability Over Current Knowledge</h2>
<p>Technology changes rapidly, which means the specific skills someone has today matter less than their ability to acquire new skills as needed. The engineers who thrive long-term are those who stay curious and adapt effectively to new challenges.</p>
<p>During interviews, I ask candidates about times they had to learn something completely new for a project. How did they approach unfamiliar technologies? What resources did they use? How did they validate their understanding? The process they describe reveals more about their potential than any specific technology they currently know.</p>
<p>I also look for evidence of intellectual humility. Can candidates acknowledge the limits of their knowledge? Do they ask thoughtful questions? Are they excited about learning from more experienced team members? Engineers who combine confidence in their abilities with openness to learning tend to grow quickly and integrate well with existing teams.</p>
<h2 id="what-this-means-for-your-hiring-process">What This Means for Your Hiring Process</h2>
<p>Identifying these qualities requires a different approach than traditional technical interviews. Instead of algorithm problems, focus on discussing real projects and technical decisions. Instead of whiteboard coding, review actual code they’ve written and ask them to explain their thinking.</p>
<p>Spend time on behavioral questions that reveal collaborative patterns and learning ability. Make time for informal conversation about what kind of work environment they thrive in and what they’re excited to learn next.</p>
<p>Most importantly, involve your team in the hiring process. The people who will work directly with your new hire are often better at assessing team fit than individual interviewers making isolated decisions.</p>
<p>Remember that hiring is ultimately about predicting future success, not just evaluating current abilities. The candidates who can build complete projects, think systematically about technical decisions, communicate effectively, and continue learning will contribute more to your team’s long-term success than those who simply perform well on coding tests.</p>
<p>Your perfect candidate isn&rsquo;t necessarily the most technically skilled or the most knowledgeable about your domain. It&rsquo;s the person who will grow with your team and contribute to the kind of collaborative, effective engineering culture that retains great people and delivers great software.</p>
]]></content></entry><entry><title type="html">How to become a software developer</title><link href="https://victoria.dev/archive/how-to-become-a-software-developer/"/><id>https://victoria.dev/archive/how-to-become-a-software-developer/</id><author><name>Victoria Drake</name></author><published>2021-01-05T04:50:07-06:00</published><updated>2021-01-05T04:50:07-06:00</updated><content type="html"><![CDATA[<p>As a Director of Engineering, I’m a software developer who hires and leads other software developers. It’s not surprising then that I get asked this question a lot, in various forms:</p>
<ul>
<li><em>How do I become a software developer?</em></li>
<li><em>What language or framework should I learn first?</em></li>
<li><em>Where do I start?</em></li>
</ul>
<p>While I’m certain there’s no one right answer for everyone, I’m also certain that the world needs more software developers and systems thinkers.</p>
<p>The best thing I can do to help you lead yourself, learn to code, and become a software developer is to share the most efficient parts of how I did it myself. This is the article I wish I had read when I started coding.</p>
<h2 id="depth-matters">Depth matters</h2>
<p><img src="depth.png" alt="Comic of an iceberg with &ldquo;Enough to build a web app&rdquo; on top and &ldquo;The really interesting stuff&rdquo; on the bottom."></p>
<p>Software is exceedingly complex. Like a good novel that you wish you’d never finish reading, there’s always more to discover and learn. If you don’t want to miss the best parts, don’t be satisfied with surface-level explanations. Always go deeper! Ask why, why, and why again until you get to the fundamentals. Soon enough, you’ll start to see patterns.</p>
<p>By digging deeper, you’ll begin to understand the fundamentals of how things connect, what makes things “fast,” and facets of software operation that you probably can’t even imagine exist. It’s like peeking behind the curtain and seeing a whole world of systems and processes that most people are never aware of.</p>
<p>Going in-depth can expand your mind and your capacity for learning. Keep asking why. Follow every link. Let your curiosity guide you.</p>
<h2 id="hard-stuff-matters">Hard stuff matters</h2>
<p>Giving yourself the chance to be delighted through discovery doesn’t come for free. It takes a lot of hard work to read and compress complicated ideas into your meat brain.</p>
<p><img src="hard-stuff.png" alt="Comic of hammering hard things into your head"></p>
<p>It’s important not to gloss over the hard stuff. In fact, if something seems too hard to understand, you might benefit from doing it first. You might have to get creative to find ways to explain things to yourself, but when you succeed, it makes everything else easier later on.</p>
<p>Analogies are helpful for understanding hard concepts, but they’ll only help you start to understand concepts at a surface level. Remember to go in-depth. Don&rsquo;t stop at the analogy.</p>
<h2 id="writing-matters">Writing matters</h2>
<p>Write right away. Create a habit of explaining everything you learn to yourself in long-form writing. Better than bullet points, writing with a conversational tone engages parts of your brain that help you to process and remember new information. It’s why humans like and remember stories, and it’s a superpower you get for free.</p>
<p>Start by writing for yourself. Write about what interests you. Try something new, even if it seems rudimentary, and write in-depth about what you learn. (<a href="/blog/iteration-in-python-for-list-and-map/">One of my most popular posts</a> is about iteration in Python. When I first wrote it, I considered myself a complete beginner.)</p>
<p>If you want to go a step further, share your writing with the world!. Learn in public, like I do by writing on this site. I often get questions like, &ldquo;how do I choose a theme for my blog?&rdquo; or &ldquo;what platform should I use?&rdquo; or &ldquo;what popular language/framework/topic should I focus on?&rdquo; My answer is: don&rsquo;t worry about it.</p>
<p>Don&rsquo;t fret too much about your blog theme or platform. Pick the easiest option for you to get started with for now. All of that will change and improve as you learn, practice, and find your focus. Just start writing, ideally, yesterday.</p>
<p>Write for yourself by explaining what you’re doing, as if it were past-you teaching future-you — because it is. You will be your first reader, and the first judge of how useful your blog can be. Seek to impress yourself!</p>
<h2 id="the-language-framework-or-version-doesnt-matter">The language, framework, or version doesn’t matter</h2>
<p>Why pigeonhole your abilities before you even start? Pick any software language, framework, or technology that seems to make sense to you when you first read it. Start there.</p>
<p>Remember that it’s important to dig deep and understand the fundamentals. Basic concepts of software transcend languages. Whichever first language you choose, understand functions, variables, return values, iteration, and how immutability works. You’ll find that learning these concepts will make it easier to recognize them in your second language, and learn that too.</p>
<h2 id="your-portfolio-doesnt-matter">Your portfolio doesn’t matter</h2>
<p>If your first objective is to build a portfolio, you may be trying to run before you walk. Building a portfolio to showcase to potential employers is a great goal, but a terrible first step.</p>
<p>If you think of creating a polished portfolio as a first step, you’re liable to spend too much time making it pretty and presentable before focusing on the content. As someone who hires software developers, I can tell you wholeheartedly that I’d rather see clean and well-written code than a flashy front page.</p>
<p><img src="temp-portfolio.png" alt="A comic of an in-progress portfolio, text reads, &ldquo;Portfolio in progress. Watch me build at GitHub.com/me&rdquo;"></p>
<p>Don’t confuse building a portfolio with building projects. Absolutely build projects, right from the beginning. There’s no better way to see the practical application of what you’re learning. Just treat them as first drafts, as training ground, and don’t worry about packaging them up for professional consumption.</p>
<p>By allowing yourself to build some draft projects first, you allow yourself the breathing room to learn from them. Focus on iteration, on making one small thing better each time, and you&rsquo;ll build a portfolio without even realizing it.</p>
<h2 id="focus-on-what-matters">Focus on what matters</h2>
<p>Don’t follow this advice blindly; rather, incorporate it into your own systems. Experiment, make it work better than when you found it, then pay it forward by writing down what you&rsquo;ve learned for someone else to read!</p>
<p>Here are my favorite books for reading or listening to if you want to cultivate a learning mindset. See <a href="/bookshelf/#non-coding-books-for-coders">non-coding books for coders</a>.</p>
<p>If this article benefits you in some way, I encourage you to write about it! The process of learning how to learn is never finished. You can be the next iteration.</p>
]]></content></entry><entry><title type="html">Be brave and build in public</title><link href="https://victoria.dev/archive/be-brave-and-build-in-public/"/><id>https://victoria.dev/archive/be-brave-and-build-in-public/</id><author><name>Victoria Drake</name></author><published>2020-12-24T04:57:31-06:00</published><updated>2020-12-24T04:57:31-06:00</updated><content type="html"><![CDATA[<p>I used to think that when I wanted to make updates to a project, I ought to hold back and do a big re-launch with all the changes at once. I figured that seeing big changes would feel like Christmas!</p>
<p>Unfortunately, Christmas only comes once a year. After years in the tech and cybersecurity world, my perspective has changed.</p>
<p>I&rsquo;ve found that people, including myself, value receiving small, constant, incremental improvements far more than big changes once or a few times a year. It makes sense if you think about it. The former constantly delights in small, unexpected ways that make the user experience better. The latter is invisible, except for a few times a year.</p>
<p>There are occasions when big changes make sense. Say you&rsquo;re re-launching functionality, or coinciding with an event that deserves all the fanfare of an unveiling.</p>
<p>Other than that, and for most of us, holding back doesn&rsquo;t serve us at all. It may even come from something far more insidious: fear of judgement.</p>
<h2 id="being-brave">Being brave</h2>
<p>Thinking such as &ldquo;I&rsquo;ll show it to the world when it&rsquo;s ready,&rdquo; always leaves out the most important detail. What does &ldquo;ready&rdquo; mean?</p>
<p><img src="mvp-vs-user.png" alt="Your MVP (a chandelier) vs. What Users Want (a lightbulb) comic"></p>
<p>If you haven&rsquo;t written down your definition of &ldquo;ready,&rdquo; consider that you may be holding back for no good reason. What&rsquo;s the worst that could happen, anyway, if you make your work public when it&rsquo;s less than perfect?</p>
<p>I decided to find out when I started to build in public. Instead of holding back work, I released a first version as soon as it functioned as intended. I leaned on <code>v0.0.*</code> tags as a way to say, &ldquo;This is available, but still in progress.&rdquo; Or, I&rsquo;d say so outright, in the README.</p>
<p>In the world of open source, building in public can be scary stuff. It feels like making yourself vulnerable. It&rsquo;s opening up part of yourself, a creative part, for scrutiny and nitpicking &ndash; <em>by strangers</em>. Of course it&rsquo;s not comfortable.</p>
<p>Once I overcame the discomfort, once I decided to be brave and appreciate even possibly negative feedback, something amazing happened.</p>
<p>I suddenly had <em>help</em>.</p>
<p>Yes, there was scrutiny and nitpicking &ndash; but I don&rsquo;t think any of it was ill-intentioned. I found that there existed whole communities of people who wanted to help me build a project that they thought was interesting. In some cases, I was utterly amazed when people submitted pull requests for issues I&rsquo;d opened <em>on my own projects</em> describing enhancements I&rsquo;d like to have.</p>
<p>I&rsquo;ve been fortunate to have wonderful experiences with open source so far. Based on these experiences, I&rsquo;d like to share with you what I&rsquo;ve discovered to be the most effective ways to be brave and generous when it comes to open source.</p>
<h2 id="tis-the-season-to-share-generously">&lsquo;Tis the season to share generously</h2>
<p>When unprompted strangers submit helpful comments, issues, and pull requests on your projects, it feels like Christmas. You can give the gift of helpfulness in your contributions as well.</p>
<p>Treat comments like a face-to-face conversation. Greet the person you&rsquo;re addressing. Use full sentences. Think about whether what you&rsquo;re writing will make someone&rsquo;s day better or worse, and be nice.</p>
<p>When writing issues, include as much technical detail as possible. Screenshots, console logs, screenshots <em>of</em> console logs, your operating system, browser, screen resolution &ndash; all these can help maintainers quickly diagnose a root cause.</p>
<p>Pull requests are the best presents ever. They make maintainers happy when well done, and it&rsquo;s a gift that gives back when your contribution gets merged! 🎉 Give your PR the best chance of getting accepted by looking for and following any project contribution guidelines.</p>
<h2 id="recognize-the-human">Recognize the human</h2>
<p>Our brains are slightly lacking in an evolutionary sense when it comes to interacting with other humans through tiny screens. It can be easy to forget that the actions you put out there will eventually reach one or more other people.</p>
<p>You can help to maintain a great open source community by remembering the humans that make it exist. When commenting, take the time to do it well (see below) and recognize the time that someone else has put in. When closing a thread or merging a contribution, remember to say thank you to the people who pitched in to help. I try to use first names instead of screen names, whenever possible.</p>
<p>You can build personal relationships, too. If you&rsquo;re a project maintainer, you may choose to give people a way to contact you directly to ask questions or hash out complicated plans. Establishing one-to-one communications with regular contributors is also a great way to build a community around your project.</p>
<p>Recognizing the humans behind the open source community is a simple and meaningful way to give back.</p>
<h2 id="dont-rush">Don&rsquo;t rush</h2>
<p>The vast majority of open source participants are volunteers, which means they don&rsquo;t get paid for the time they spend building up projects. That sometimes means that other work takes priority. It&rsquo;s okay if this describes you, too.</p>
<p>It&rsquo;s important to remember that in most cases, a well-done contribution later is preferred over a half-done contribution sooner. If you&rsquo;re too short on time now to write a thoughtful comment &ndash; don&rsquo;t! Either draft a quick note and set it aside for later, or comment something along the lines of:</p>
<blockquote>
<p>Hi there! Just wanted to let you know that I&rsquo;ve seen this and I plan to help! I&rsquo;ll respond in full as soon as I have the time to write a thoughtful comment.</p>
</blockquote>
<p>Showing that you think a comment is worth the time to do well is something that open source contributors and repository maintainers both appreciate.</p>
<h2 id="build-generously">Build generously</h2>
<p>When open source participants act with conscientiousness, every day feels like Christmas. Regardless of your type of contribution, you can help build this generous global community year-round.</p>
<p>The humans of open source, by self selection, mostly consist of good people who want to help. If you build openly, share feedback generously, and try to do good in general, I think you fit in here.</p>
<p>I hope you have a very happy holiday season and give many gifts that keep on giving!</p>
]]></content></entry><entry><title type="html">So you&amp;#39;re the family tech support</title><link href="https://victoria.dev/archive/so-youre-the-family-tech-support/"/><id>https://victoria.dev/archive/so-youre-the-family-tech-support/</id><author><name>Victoria Drake</name></author><published>2020-12-21T08:42:24-05:00</published><updated>2020-12-21T08:42:24-05:00</updated><content type="html"><![CDATA[<p>🎄🌟 Happy holidays! 🌟🎄</p>
<p>For those of you seeing relatives this season, chances are that you’re the designated family tech support. If part of your time home for the holidays is spent on software updates and troubleshooting WiFi, here are a few other quick wins to help boost your family&rsquo;s online privacy and security.</p>
<h2 id="1-set-up-a-vpn">1. Set up a VPN</h2>
<p>Using a VPN is Online Safety 101. Choose a reputable provider with a strict no-logging policy, or if you&rsquo;re up for it, <a href="/blog/set-up-a-pi-hole-vpn-on-an-aws-lightsail-instance/">roll your own</a>.</p>
<h2 id="2-introduce-a-password-manager">2. Introduce a password manager</h2>
<p>If your family member uses the same password everywhere (<code>&lt;petname&gt;</code>+<code>&lt;house number&gt;</code>, same as last year) because passwords are hard to remember, introduce them to their new best friend, <a href="https://1password.com/">1Password</a>. Help your family get set up with secure passwords they don&rsquo;t have to write down on Post-It notes &ndash; just one master pass(phrase) is all you need.</p>
<p>When choosing a passphrase, avoid using information easily found on social media accounts, like pet names, favorite sports teams, favorite brands, or birthdays.</p>
<h2 id="3-switch-to-duckduckgo">3. Switch to DuckDuckGo</h2>
<p>Help fight the Internet search monopoly by getting your family to use a search engine that respects their privacy. Go to your browser <strong>Settings</strong> and set your <strong>Default Search Engine</strong> (that uses the URL bar) to <a href="https://duckduckgo.com/">DuckDuckGo</a>. Break the ice with an instant answer feature, like searching &ldquo;calendar&rdquo; so you can <a href="https://duckduckgo.com/?q=Countdown+To+25th+dec+2020&amp;t=canonical&amp;ia=answer">count down to Christmas</a>.</p>
<p>(You might want to search for &ldquo;classic cocktails cheat sheet&rdquo; after all this.)</p>
<h2 id="4-install-a-better-browser-and-blocker">4. Install a better browser and blocker</h2>
<p>While I prefer a <a href="https://pi-hole.net/">Pi-hole</a>, setting one up can be complex. Instead, help set up a privacy-preserving browser like <a href="https://www.mozilla.org/en-US/firefox/new/">Firefox</a> or a wide-spectrum blocking extension like <a href="https://ublockorigin.com/">uBlock Origin</a> <a href="https://github.com/gorhill/uBlock">(GitHub source)</a>.</p>
<p>Your family will get faster page load times, less advertisements interrupting articles and videos, and fewer sneaky trackers leaking browsing habits to big tech, all with near-zero maintenance.</p>
<h2 id="be-a-home-for-the-holidays-hero">Be a home-for-the-holidays hero!</h2>
<p>Help improve your family&rsquo;s security posture this holiday season. A little beefed-up cybersecurity may be one of the best gifts you can give!</p>
<p>I&rsquo;m keeping it short-and-sweet this week. My annual Christmas post drops on December 24, full of warm fuzzy goodness and a tech tip or two. Thank you for <a href="/">being a subscriber</a> &ndash; stay tuned!</p>
]]></content></entry><entry><title type="html">How to Write Good Documentation</title><link href="https://victoria.dev/archive/how-to-write-good-documentation/"/><id>https://victoria.dev/archive/how-to-write-good-documentation/</id><author><name>Victoria Drake</name></author><published>2020-12-14T04:53:10-05:00</published><updated>2020-12-14T04:53:10-05:00</updated><content type="html"><![CDATA[<p>If you&rsquo;ve ever half-written a software project before taking a few days off, this is the article you&rsquo;ll discover you needed when you reopen that IDE.</p>
<p><img src="friday-monday.png" alt="Your project on Friday (a finished puzzle) vs Monday (a pile of puzzle pieces) comic"></p>
<p>In the technology teams I lead, we make a constant effort to document all the things. Documentation lives alongside the code as an equal player. This helps ensure that no one needs to make assumptions about how something works, or is calling lengthy meetings to gain working knowledge of a feature. Good documentation saves us a lot of time and hassle.</p>
<p>That said, and contrary to popular belief, the most valuable software documentation is not primarily written for other people. As I said in this well-received tweet:</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">The secret to good documentation is to write it while you&#39;re writing the code. You are your first audience. Explain what you&#39;re doing to yourself. Future you will thank you!</p>&mdash; Victoria Drake <a href="https://twitter.com/victoriadotdev/status/1331262801797652483?ref_src=twsrc%5Etfw">November 24, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>Here are three concrete steps you can take to write good documentation before it&rsquo;s too late.</p>
<h2 id="1-start-with-accurate-notes">1. Start with accurate notes</h2>
<p>As you work out ideas in code, ensure you don’t soon forget important details by starting with accurate notes. While you will want to explain things to yourself in long-form later, short-form notes will suffice to capture details without interrupting your coding session flow.</p>
<p>Don&rsquo;t rely on inline comments that often fail to make sense once you&rsquo;ve forgotten the context. Keep a document open alongside your code and write down things like commands, decisions, and sources you use. This can include:</p>
<ul>
<li>Prompts or shell commands you used</li>
<li>Why you chose a particular method over another</li>
<li>Links you visited for help or <em>cough</em>copy-paste<em>cough</em> inspiration</li>
<li>The order in which you did things</li>
</ul>
<p>Don’t worry about full sentences at this point. Just ensure you accurately capture context, relevant code snippets, and helpful URLs. It can also be helpful to turn on any auto-save option available.</p>
<h2 id="2-explain-decisions-in-long-form">2. Explain decisions in long form</h2>
<p>The ideal time to tackle this step is when you take a break from coding, but before you completely go out to lunch on whatever it is you’re working on at the moment. You want to ensure that context, ideas, and decisions are all still fresh in your mind when you explain them to yourself.</p>
<p>Go over the short-form notes you took and start expanding them into conversational writing. Be your own rubber duck. Describe what you’re doing as if you were teaching it to someone else. You might cover topics such as:</p>
<ul>
<li>Quirky-looking decisions: &ldquo;I would normally do it this way, but I chose to do something different because&hellip;&rdquo;</li>
<li>Challenges you ran into and how you overcame them</li>
<li>Architectural decisions that support your project goals</li>
</ul>
<p>Stick to the main points. Long-form writing doesn’t mean you’ll be paid by the word! Just use full sentences, and write as if explaining your project to a colleague. You’re explaining to future you, after all.</p>
<h2 id="3-dont-neglect-prerequisite-knowledge">3. Don&rsquo;t neglect prerequisite knowledge</h2>
<p>This step is best done after a long lunch break, or even the next day (but probably not two). Re-read your document and fill in any blanks that become apparent after putting some distance between yourself and the project.</p>
<p>Take extra care to fill in or at least link to prerequisite knowledge, especially if you frequently use different languages or tools. Even an action as small as pasting in a link to the API documentation you used can save hours of future searching.</p>
<p>Write down or link to READMEs, installation steps, and relevant support issues. For frequently performed command-line actions, you can use a <a href="/posts/how-to-create-a-self-documenting-makefile/">self-documenting Makefile</a> to avoid having to <code>man</code> common tasks each time you come back to a project.</p>
<p>It’s easy to forget supporting details after even just a short break from your project. Capture anything you found helpful this time around.</p>
<h2 id="document-all-the-things">Document all the things</h2>
<p>The next time you catch yourself thinking, “I’m sure I’ll remember this part, no need to write it down,” just recall this emoji: 🤦‍♀️</p>
<p>Software projects are made up of a lot more than just their code. To best set up your future self for success, document all the things! Whether it’s a process you’ve established, Infrastructure as Code, or a fleeting future roadmap idea — write it down! Future you will thank you for it.</p>
<p>If you enjoyed this post, there&rsquo;s a lot more where that came from! I write about developer ergonomics for high-performing teams and building beautiful, maintainable software in the age of AI. You can subscribe below to see new posts first.</p>
<a href="https://medium.com/@victoriadotdev/subscribe" target="_blank" rel="noopener noreferrer" class="subscribe-button">
    <span class="subscribe-icon">📧</span>
    <span class="subscribe-text">Subscribe</span>
</a>

]]></content></entry><entry><title type="html">Do One Thing: Mastering Prioritization for High-Performing Teams</title><link href="https://victoria.dev/posts/do-one-thing-mastering-prioritization-for-high-performing-teams/"/><id>https://victoria.dev/posts/do-one-thing-mastering-prioritization-for-high-performing-teams/</id><author><name>Victoria Drake</name></author><published>2020-12-07T15:01:25-06:00</published><updated>2020-12-07T15:01:25-06:00</updated><content type="html"><![CDATA[<p>In the engineering teams I lead, “priority” has no plural form. This drives some people slightly crazy, especially those who like to hedge their bets with phrases like “top priorities” or “critical priorities.” But I’ve learned that the moment you allow multiple top priorities, you’ve essentially created zero priorities.</p>
<p>I discovered this the hard way while working with a team that was constantly context-switching between “urgent” projects. Everyone was busy, morale was decent, but we weren’t actually shipping much of value. During one particularly frustrating week, I counted seventeen different tasks that had been labeled as “high priority” by various stakeholders. Our standups felt like disaster reports, and I realized we’d created a system where being busy had become more important than being effective.</p>
<p>The solution turned out to be surprisingly simple, though not easy to implement: put everything into a single, ordered list where only one thing can be most important at any given time.</p>
<h2 id="the-radical-transparency-of-a-central-list">The Radical Transparency of a Central List</h2>
<p>Most teams I’ve encountered operate like a collection of individual to-do lists with some coordination meetings sprinkled on top. Engineering works on technical debt, product pushes for new features, leadership wants infrastructure improvements, and everyone optimizes their own piece of the puzzle. The result is a lot of activity that doesn’t add up to meaningful progress.</p>
<p><img src="prioritize.png" alt="A cartoon of a stick figure swinging on a rope to plant a post-it note"></p>
<p>A single, centralized, prioritized list changes the entire dynamic. Everyone can see what’s actually being worked on, what’s coming next, and most importantly, what’s not getting done and why. This visibility creates natural conversations about trade-offs that simply don’t happen when work is siloed.</p>
<p>I’ve watched teams discover they were working on competing solutions to the same problem, simply because no one had a complete view of active work. Others realized they were delaying important projects because someone assumed “someone else” was handling the dependency. When everything is visible and ordered, these coordination problems become obvious and fixable.</p>
<p>The transparency also creates a different kind of accountability. When priorities are public and explicit, it becomes much harder to justify working on pet projects or avoiding difficult tasks. The list becomes a shared source of truth that guides decisions rather than each person interpreting priorities through their own lens.</p>
<h2 id="autonomy-within-structure">Autonomy Within Structure</h2>
<p>One concern I hear frequently is that a single priority list will turn people into order-takers rather than creative problem-solvers. In practice, I’ve found exactly the opposite happens when you implement it correctly.</p>
<p><img src="task-selection.png" alt="A cartoon of a stick figure climbing a ladder to reach a post-it note"></p>
<p>The key is encouraging people to choose the highest-priority task they can effectively tackle rather than assigning specific tasks to specific people. Someone might skip over the absolute top item because it requires domain knowledge they don’t have, but they can pick up the second or third item that lets them contribute meaningfully while learning something new.</p>
<p>This approach leverages the fact that your team members understand their own capabilities and growth goals better than you do. A senior engineer might choose to mentor a junior developer on a complex task. A frontend specialist might want to tackle a backend task to broaden their skills. These decisions create better outcomes in the long term than top-down task assignment while still maintaining focus on organizational priorities.</p>
<p>The autonomy comes from trusting people to make good decisions about how to contribute most effectively, while the structure comes from ensuring those contributions align with actual business needs.</p>
<h2 id="the-art-of-making-yourself-redundant">The Art of Making Yourself Redundant</h2>
<p>If your team frequently asks you what they should work on next, you’ve accidentally created a bottleneck—and it&rsquo;s you. This is one of the most common scaling problems I see with engineering leaders who transition from individual contributor roles.</p>
<p><img src="add-resources.png" alt="A cartoon of a stick figure carrying books to a wall of post-it notes"></p>
<p>The goal is building a system where intelligent people can make good decisions without constant input from leadership. This requires making context painfully available—team goals, product strategy, architectural decisions, customer feedback, and anything else that influences prioritization should be accessible and current.</p>
<p>I’ve found that the difference between teams that scale smoothly and teams that hit velocity walls usually comes down to how well they’ve documented the reasoning behind decisions. When someone can understand not just what to build but why it matters and how it fits into the larger strategy, they can make smart trade-offs independently.</p>
<p>This redundancy becomes especially critical during high-pressure situations. When systems are down or deadlines are looming, you don’t want your team waiting for permission to take action. Teams that have practiced autonomous decision-making within clear constraints can respond quickly and effectively without requiring heroic coordination efforts.</p>
<h2 id="the-cultural-transformation">The Cultural Transformation</h2>
<p>What surprises most leaders is how much this simple change affects team culture. When priorities are clear and transparent, several things happen that go far beyond improved task management.</p>
<p>First, political conversations about priority disappear. There’s no point in lobbying for your favorite project when the criteria for prioritization are explicit and the current order is visible to everyone. Energy that was spent on organizational maneuvering gets redirected toward actual work.</p>
<p>Second, people start thinking about their contributions differently. Instead of optimizing for individual productivity, they begin considering how their work fits into team objectives. This naturally leads to better collaboration and knowledge sharing.</p>
<p>Third, the team develops a shared sense of progress and momentum. When everyone can see important work getting completed in priority order, it creates a satisfying rhythm that isolated individual achievements can’t match.</p>
<h2 id="implementation-reality">Implementation Reality</h2>
<p>The biggest challenge isn’t creating the list—it’s maintaining the discipline to use it consistently. Teams often start strong but gradually drift back to multiple priority tracks when pressure increases or when compelling new opportunities arise.</p>
<p>I’ve learned to treat priority discipline like any other technical practice that requires ongoing attention. Schedule regular review sessions to reorder the list, have explicit discussions about what we’re choosing not to do, and consistently communicate why keeping a single-priority focus helps maintain development velocity.</p>
<p>The payoff: teams that ship more valuable work with less stress and confusion. When everyone understands what matters most and feels empowered to contribute effectively, both productivity and job satisfaction improve dramatically.</p>
<p>Most importantly, single-priority focus creates sustainable high performance rather than the boom-and-bust cycles that come from constantly shifting between competing urgent demands. Teams learn to work steadily toward important goals rather than reacting to whatever feels most pressing in the moment.</p>
]]></content></entry><entry><title type="html">OWASP Web Security Testing Guide v4.2 released</title><link href="https://victoria.dev/archive/owasp-web-security-testing-guide-v4.2-released/"/><id>https://victoria.dev/archive/owasp-web-security-testing-guide-v4.2-released/</id><author><name>Victoria Drake</name></author><published>2020-12-03T16:02:33-06:00</published><updated>2020-12-03T16:02:33-06:00</updated><content type="html"><![CDATA[<p>I&rsquo;m very happy and proud to share that the Open Web Application Security Project (OWASP) Web Security Testing Guide v4.2 is now available! This update is the result of a lot of hard work by the repository team and many dedicated contributors. With a team like this, I&rsquo;m honored to be a core maintainer and co-author.</p>
<p>Here&rsquo;s a reprint of <a href="https://owasp.org/2020/12/03/wstg-v42-released.html">the announcement I wrote for owasp.org</a>. If you&rsquo;re interested in security testing for web applications and APIs, this is an update you&rsquo;ll definitely want to check out!</p>
<p>You can become a contributor yourself by <a href="https://github.com/OWASP/wstg">joining us on GitHub</a>!</p>
<hr>
<h2 id="web-security-testing-guide-v42-released">Web Security Testing Guide v4.2 Released</h2>
<p><em>Thursday, December 3, 2020</em></p>
<p>The OWASP Web Security Testing Guide team is proud to announce version 4.2 of the Web Security Testing Guide (WSTG)! In keeping with a continuous delivery mindset, this new minor version adds content as well as improves the existing tests.</p>
<p>In recent years, the Web Security Testing Guide has sought to remain your foremost open source resource for web application testing. Our previous release marked a move from a cumbersome wiki platform to the highly collaborative world of <a href="https://github.com/OWASP/wstg/">GitHub</a>. Since then, over 61 new contributors pushing over 600 commits have helped to make the WSTG better than ever.</p>
<p>Version 4.2 of the Web Security Testing Guide introduces new testing scenarios, updates existing chapters, and offers an improved reading experience with a clearer writing style and chapter layout. Readers will enjoy easier navigation and consistent testing instructions.</p>
<p>With new improvements to our development workflow, new contributors will find it easier than ever to help build future versions of the WSTG. A clear and concise <a href="https://github.com/OWASP/wstg/blob/master/CONTRIBUTING.md">contributor’s guide</a> and <a href="https://github.com/OWASP/wstg/blob/master/style_guide.md">style guide</a> can help you write new tests or ensure existing scenarios stay current. Core maintainers <a href="https://github.com/kingthorin">Rick Mitchell</a>, <a href="https://github.com/ThunderSon">Elie Saad</a>, <a href="https://github.com/rejahrehim">Rejah Rehim</a>, and <a href="https://github.com/victoriadrake">Victoria Drake</a> have implemented modern processes like continuous integration with GitHub Actions. New workflows help to build PDFs and make reviewing new additions and updates easier.</p>
<p>We couldn’t be happier to share this new version with you, and we don’t plan to slow down anytime soon. The dedicated volunteers who’ve made this release possible are already hard at work on the next major version of the WSTG. Come <a href="https://github.com/OWASP/wstg">join us and become a contributor</a>!</p>
<p>You can <a href="https://owasp.org/www-project-web-security-testing-guide/">read the Web Security Testing Guide v4.2 online or download a PDF</a> on our project page. We greatly appreciate all the authors, editors, reviewers, and readers who make this open source security endeavor worthwhile.</p>
<p>Thank you for being a part of the WSTG!</p>
]]></content></entry><entry><title type="html">What is TCP/IP? Layers and protocols explained</title><link href="https://victoria.dev/archive/what-is-tcp/ip-layers-and-protocols-explained/"/><id>https://victoria.dev/archive/what-is-tcp/ip-layers-and-protocols-explained/</id><author><name>Victoria Drake</name></author><published>2020-11-29T04:01:22-04:00</published><updated>2020-11-29T04:01:22-04:00</updated><content type="html"><![CDATA[<p>A significant part of the process of creation is the ability to imagine things that do not yet exist. This skill was instrumental to the creation of the Internet. If no one had imagined the underlying technology that most now take for granted every day, there would be no cat memes.</p>
<p>To make the Internet possible, two things that needed imagining are <em>layers</em> and <em>protocols.</em> Layers are conceptual divides that group similar functions together. The word &ldquo;protocol,&rdquo; means &ldquo;the way we&rsquo;ve agreed to do things around here,&rdquo; more or less. In short, both layers and protocols can be explained to a five-year-old as &ldquo;ideas that people agreed sounded good, and then they wrote them down so that other people could do things with the same ideas.&rdquo;</p>
<p>The Internet Protocol Suite is described in terms of layers and protocols. Collectively, the suite refers to the communication protocols that enable our endless scrolling. It&rsquo;s often called by its foundational protocols: the Transmission Control Protocol (TCP) and the Internet Protocol (IP). Lumped together as TCP/IP, these protocols describe how data on the Internet is packaged, addressed, sent, and received.</p>
<p>Here&rsquo;s why the Internet Protocol Suite, or TCP/IP, is an imaginary rainbow layer cake.</p>
<h2 id="layers-are-imaginary">Layers are imaginary</h2>
<p>If you consider the general nature of a rainbow layer sponge cake, it&rsquo;s mostly made up of soft, melt-in-your mouth vanilla-y goodness. This goodness is in itself comprised of something along the lines of eggs, butter, flour, and sweetener.</p>
<p><img src="free-cake.png" alt="Cartoon of a slice of rainbow layer cake, reads &ldquo;Yay! Free cake!&rdquo;"></p>
<p>There isn&rsquo;t much to distinguish one layer of a rainbow sponge cake from another. Often, the only difference between layers is the food-coloring and a bit of frosting. When you think about it, it&rsquo;s all cake from top to bottom. The rainbow layers are only there because the baker thought they ought to be.</p>
<p>Similar to cake ingredients, layers in the context of computer networking are mostly composed of protocols, algorithms, and configurations, with some data sprinkled in. It can be easier to talk about computer networking if its many functions are split up into groups, so certain people came up with descriptions of layers, which we call network models. TCP/IP is just one network model among others. In this sense, layers are concepts, not things.</p>
<p>Some of the people in question are part of the Internet Engineering Task Force (IETF). They created the <a href="https://tools.ietf.org/html/rfc1122">RFC-1122</a> publication, discussing the Internet&rsquo;s communications layers. Half of a whole, the standard:</p>
<blockquote>
<p>&hellip;covers the communications protocol layers: link layer, IP layer, and transport layer; its companion <a href="https://tools.ietf.org/html/rfc1123">RFC-1123</a> covers the application and support protocols.</p>
</blockquote>
<p>The layers described by RFC-1122 and RFC-1123 each encapsulate protocols that satisfy the layer&rsquo;s functionality. Let&rsquo;s look at each of these communications layers and see how TCP and IP stack up in this model of the Internet layer cake.</p>
<h2 id="link-layer-protocols">Link layer protocols</h2>
<p><img src="link.png" alt="Link cake layer cartoon"></p>
<p>The <a href="https://datatracker.ietf.org/doc/html/rfc1122#page-21">link layer</a> is the most basic, or lowest-level, classification of communication protocol. It deals with sending information between hosts on the same local network, and translating data from the higher layers to the physical layer. Protocols in the link layer describe how data interacts with the transmission medium, such as electronic signals sent over specific hardware. Unlike other layers, link layer protocols are dependent on the hardware being used.</p>
<h2 id="internet-layer-protocols">Internet layer protocols</h2>
<p>Protocols in the <a href="https://tools.ietf.org/html/rfc1122#page-27">Internet layer</a> describe how data is sent and received over the Internet. The process involves packaging data into packets, addressing and transmitting packets, and receiving incoming packets of data.</p>
<p><img src="internet.png" alt="Internet cake layer cartoon"></p>
<p>The most widely known protocol in this layer gives TCP/IP its last two letters. IP is a connectionless protocol, meaning that it provides no guarantee that packets are sent or received in the right order, along the same path, or even in their entirety. Reliability is handled by other protocols in the suite, such as in the transport layer.</p>
<p>There are currently two versions of IP in use: IPv4, and IPv6. Both versions describe how devices on the Internet are assigned IP addresses, which are used when navigating to cat memes. IPv4 is more widely used, but has only <a href="https://tools.ietf.org/html/rfc791#section-2.3">32 bits for addressing</a>, allowing for about 4.3 billion (ca. 4.3×10<sup>9</sup>) possible addresses. These are running out, and IPv4 and will eventually suffer from address exhaustion as more and more people use more devices on the Internet.</p>
<p>The successor version IPv6 aims to solve address exhaustion by <a href="https://tools.ietf.org/html/rfc8200#section-1">using 128 bits for addresses</a>. This provides, um, a <em>lot</em> more address possibilities (ca. 3.4×10<sup>38</sup>).</p>
<h2 id="transport-layer-protocols">Transport layer protocols</h2>
<p>In May 1974, Vint Cerf and Bob Kahn (collectively often called &ldquo;the fathers of the Internet&rdquo;) published a paper entitled <a href="https://web.archive.org/web/20160304150203/http://ece.ut.ac.ir/Classpages/F84/PrincipleofNetworkDesign/Papers/CK74.pdf">A Protocol for Packet Network Intercommunication</a>. This paper contained the first description of a Transmission Control Program, a concept encompassing what would eventually be known as the Transmission Control Protocol (TCP) and User Datagram Protocol (UDP). (I had the pleasure of meeting Vint and can personally confirm that yes, he does look exactly like The Architect in the Matrix movies.)</p>
<p><img src="transport.png" alt="Transport cake layer cartoon"></p>
<p>The <a href="https://tools.ietf.org/html/rfc1122#page-77">transport layer</a> presently encapsulates TCP and UDP. Like IP, UDP is connectionless and can be used to prioritize time over reliability. TCP, on the other hand, is a connection-oriented transport layer protocol that prioritizes reliability over latency, or time. TCP describes transferring data in the same order as it was sent, retransmitting lost packets, and controls affecting the rate of data transmission.</p>
<h2 id="application-layer-protocols">Application layer protocols</h2>
<p><img src="application.png" alt="Application cake layer cartoon"></p>
<p>The application layer describes the protocols that software applications interact with most often. The specification includes descriptions of the remote login protocol <a href="https://tools.ietf.org/html/rfc1123#section-3">Telnet</a>, the <a href="https://tools.ietf.org/html/rfc1123#section-4">File Transfer Protocol (FTP)</a>, and the <a href="https://tools.ietf.org/html/rfc1123#section-5">Simple Mail Transfer Protocol (SMTP)</a>.</p>
<p>Also included in the application layer are the Hypertext Transfer Protocol (HTTP) and its successor, Hypertext Transfer Protocol Secure (HTTPS). HTTPS is secured by Transport Layer Security, or TLS, which can be said to be the top-most layer of the networking model described by the Internet protocol suite. If you&rsquo;d like to further understand TLS and how this protocol secures your cat meme viewing, I invite you <a href="/blog/tls">read my article about TLS and cryptography</a>.</p>
<h2 id="the-internet-cake-is-still-baking">The Internet cake is still baking</h2>
<p>Like a still-rising sponge cake, descriptions of layers, better protocols, and new models are being developed every day. The Internet, or whatever it will become in the future, is still in the process of being imagined.</p>
<p><img src="cake.png" alt="Cartoon of the full Internet layer cake, topped with Nyan Cat memes"></p>
<p>If you enjoyed learning from this post, there&rsquo;s a lot more where this came from! I write about computing, cybersecurity, and building great technical teams. You can subscribe below to see new posts first.</p>
<a href="https://medium.com/@victoriadotdev/subscribe" target="_blank" rel="noopener noreferrer" class="subscribe-button">
    <span class="subscribe-icon">📧</span>
    <span class="subscribe-text">Subscribe</span>
</a>

]]></content></entry><entry><title type="html">Responsive pages and color themes with minimal CSS</title><link href="https://victoria.dev/archive/responsive-pages-and-color-themes-with-minimal-css/"/><id>https://victoria.dev/archive/responsive-pages-and-color-themes-with-minimal-css/</id><author><name>Victoria Drake</name></author><published>2020-11-17T06:04:58-05:00</published><updated>2020-11-17T06:04:58-05:00</updated><content type="html"><![CDATA[<p>Hello, do come in! If you&rsquo;re reading this on my website, you may notice I&rsquo;ve spruced up a bit. <a href="/">Victoria.dev</a> can now better respond to your devices and preferences!</p>
<p>Most modern devices and web browsers allow users to choose either a light or dark theme for the user interface. With <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries">CSS media queries</a>, you can have your own website&rsquo;s styles change to match this user setting!</p>
<p>Media queries are also a common way to have elements on web pages change to suit different screen sizes. This is an especially powerful tool when combined with <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/--*">custom properties</a> set on the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:root">root element</a>.</p>
<p>Here&rsquo;s how to use CSS media queries and custom properties to improve your visitor&rsquo;s browsing experience with just a few lines of CSS.</p>
<h2 id="catering-to-color-preferences">Catering to color preferences</h2>
<p>The <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme"><code>prefers-color-scheme</code> media feature</a> can be queried to serve up your user&rsquo;s color scheme of choice. The <code>light</code> option is the go-to version if no active preference is set, and it has <a href="https://caniuse.com/mdn-css_at-rules_media_prefers-color-scheme">decent support across modern browsers</a>.</p>
<p>Additionally, users reading on certain devices can also set light and dark color themes based on a schedule. For example, my phone uses light colors throughout its UI during the daytime, and dark colors at night. You can make your website follow suit!</p>
<p>Avoid repeating a lot of CSS by setting custom properties for your color themes on your <code>:root</code> pseudo-class. You can specify the themes available with the <a href="https://drafts.csswg.org/css-color-adjust/#color-scheme-prop"><code>color-scheme</code> property</a> (currently part of a <a href="https://drafts.csswg.org/css-color-adjust-1/">draft specification</a>, but I like to write my articles to age well). Create a version for each theme you wish to support. Here&rsquo;s a quick example you can build on:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-css" data-lang="css"><span style="display:flex;"><span>:<span style="color:#a6e22e">root</span> {
</span></span><span style="display:flex;"><span>    color-scheme: light dark;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>@<span style="color:#66d9ef">media</span> <span style="color:#f92672">(</span><span style="color:#f92672">prefers-color-scheme</span><span style="color:#f92672">:</span> <span style="color:#f92672">light</span><span style="color:#f92672">)</span> {
</span></span><span style="display:flex;"><span>    :<span style="color:#a6e22e">root</span> {
</span></span><span style="display:flex;"><span>        --text-primary: <span style="color:#ae81ff">#24292e</span>;
</span></span><span style="display:flex;"><span>        --background: <span style="color:#66d9ef">white</span>;
</span></span><span style="display:flex;"><span>        --shadow: rgba(<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">0.15</span>) <span style="color:#ae81ff">0</span><span style="color:#66d9ef">px</span> <span style="color:#ae81ff">2</span><span style="color:#66d9ef">px</span> <span style="color:#ae81ff">5</span><span style="color:#66d9ef">px</span> <span style="color:#ae81ff">0</span><span style="color:#66d9ef">px</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>@<span style="color:#66d9ef">media</span> <span style="color:#f92672">(</span><span style="color:#f92672">prefers-color-scheme</span><span style="color:#f92672">:</span> <span style="color:#f92672">dark</span><span style="color:#f92672">)</span> {
</span></span><span style="display:flex;"><span>    :<span style="color:#a6e22e">root</span> {
</span></span><span style="display:flex;"><span>        --text-primary: <span style="color:#66d9ef">white</span>;
</span></span><span style="display:flex;"><span>        --background: <span style="color:#ae81ff">#24292e</span>;
</span></span><span style="display:flex;"><span>        --shadow: rgba(<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">0.35</span>) <span style="color:#ae81ff">0</span><span style="color:#66d9ef">px</span> <span style="color:#ae81ff">2</span><span style="color:#66d9ef">px</span> <span style="color:#ae81ff">5</span><span style="color:#66d9ef">px</span> <span style="color:#ae81ff">0</span><span style="color:#66d9ef">px</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>As you can see, you can use custom properties to set all kinds of values. To use these as variables with other CSS elements, use the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/var()"><code>var()</code> function</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-css" data-lang="css"><span style="display:flex;"><span><span style="color:#f92672">header</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">color</span>: <span style="color:#a6e22e">var</span>(<span style="color:#f92672">--</span><span style="color:#66d9ef">text</span><span style="color:#f92672">-</span>primary);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">background-color</span>: <span style="color:#a6e22e">var</span>(<span style="color:#f92672">--</span>background);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">box-shadow</span>: <span style="color:#a6e22e">var</span>(<span style="color:#f92672">--</span>shadow);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>In this quick example, the <code>header</code> element will now display your user&rsquo;s preferred colors according to their browser settings!</p>
<p>Preferred color schemes are set by the user in different ways, depending on the browser. Here are a couple examples.</p>
<h3 id="firefox">Firefox</h3>
<p>You can test out <code>light</code> and <code>dark</code> modes in Firefox by typing <code>about:config</code> into the address bar. Accept the warning if it pops up, then type <code>ui.systemUsesDarkTheme</code> into the search.</p>
<p>Choose a <code>Number</code> value for the setting, then input a <code>1</code> for dark or <code>0</code> for light.</p>
<p><img src="firefox-theme-setting.png" alt="A screenshot of setting the color theme in Firefox"></p>
<h3 id="brave">Brave</h3>
<p>If you&rsquo;re using Brave, find color theme settings in <strong>Settings</strong> &gt; <strong>Appearance</strong> &gt; <strong>Brave colors</strong>.</p>
<p><img src="brave-settings.png" alt="A screenshot of setting the color theme in Brave"></p>
<h2 id="variable-scaling">Variable scaling</h2>
<p>You can also use a custom property to effortlessly adjust the size of text or other elements depending on your user&rsquo;s screen size. The <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/width"><code>width</code> media feature</a> tests the width of the viewport. While <code>width: _px</code> will match an exact size, you can also use <code>min</code> and <code>max</code> to create ranges.</p>
<p>Query with <code>min-width: _px</code> to match anything over <code>_</code> pixels, and <code>max-width: _px</code> to match anything up to <code>_</code> pixels.</p>
<p>Use these queries to set a custom property on the <code>:root</code> to create a ratio:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-css" data-lang="css"><span style="display:flex;"><span>@<span style="color:#66d9ef">media</span> <span style="color:#f92672">(</span><span style="color:#f92672">min-width</span><span style="color:#f92672">:</span> <span style="color:#f92672">360px</span><span style="color:#f92672">)</span> {
</span></span><span style="display:flex;"><span>    :<span style="color:#a6e22e">root</span> {
</span></span><span style="display:flex;"><span>        --scale: <span style="color:#ae81ff">0.8</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>@<span style="color:#66d9ef">media</span> <span style="color:#f92672">(</span><span style="color:#f92672">min-width</span><span style="color:#f92672">:</span> <span style="color:#f92672">768px</span><span style="color:#f92672">)</span> {
</span></span><span style="display:flex;"><span>    :<span style="color:#a6e22e">root</span> {
</span></span><span style="display:flex;"><span>        --scale: <span style="color:#ae81ff">1</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>@<span style="color:#66d9ef">media</span> <span style="color:#f92672">(</span><span style="color:#f92672">min-width</span><span style="color:#f92672">:</span> <span style="color:#f92672">1024px</span><span style="color:#f92672">)</span> {
</span></span><span style="display:flex;"><span>    :<span style="color:#a6e22e">root</span> {
</span></span><span style="display:flex;"><span>        --scale: <span style="color:#ae81ff">1.2</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Then make an element responsive by using the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/calc()"><code>calc()</code> function</a>. Here are a few examples:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-css" data-lang="css"><span style="display:flex;"><span><span style="color:#f92672">h1</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">font-size</span>: calc(<span style="color:#ae81ff">42</span><span style="color:#66d9ef">px</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">var</span>(<span style="color:#f92672">--</span>scale));
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">h2</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">font-size</span>: calc(<span style="color:#ae81ff">26</span><span style="color:#66d9ef">px</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">var</span>(<span style="color:#f92672">--</span>scale));
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">img</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">width</span>: calc(<span style="color:#ae81ff">200</span><span style="color:#66d9ef">px</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">var</span>(<span style="color:#f92672">--</span>scale));
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>In this example, multiplying an initial value by your <code>--scale</code> custom property allows the size of headings and images to magically adjust to your user&rsquo;s device width.</p>
<p>The relative unit <code>rem</code> will have a similar effect. You can use it to define sizes for elements relative to the font size declared at the root element.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-css" data-lang="css"><span style="display:flex;"><span><span style="color:#f92672">h1</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">font-size</span>: calc(<span style="color:#ae81ff">5</span><span style="color:#66d9ef">rem</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">var</span>(<span style="color:#f92672">--</span>scale));
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">h2</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">font-size</span>: calc(<span style="color:#ae81ff">1.5</span><span style="color:#66d9ef">rem</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">var</span>(<span style="color:#f92672">--</span>scale));
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">p</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">font-size</span>: calc(<span style="color:#ae81ff">1</span><span style="color:#66d9ef">rem</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">var</span>(<span style="color:#f92672">--</span>scale));
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Of course, you can also multiply two custom properties. For example, setting the <code>--max-img</code> as a custom property on the <code>:root</code> can help to save you time later on by not having to update a pixel value in multiple places:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-css" data-lang="css"><span style="display:flex;"><span><span style="color:#f92672">img</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">max-width</span>: calc(<span style="color:#a6e22e">var</span>(<span style="color:#f92672">--</span>max<span style="color:#f92672">-</span>img) <span style="color:#f92672">*</span> <span style="color:#a6e22e">var</span>(<span style="color:#f92672">--</span>scale));
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h2 id="raise-your-responsiveness-game">Raise your responsiveness game</h2>
<p>Try out these easy wins for a website that caters to your visitor&rsquo;s devices and preferences. I&rsquo;ve put them to good use now on <a href="/">victoria.dev</a>. I invite you to <a href="/contact">let me know how you like it!</a></p>
]]></content></entry><entry><title type="html">Build your own serverless subscriber list with Go and AWS</title><link href="https://victoria.dev/archive/build-your-own-serverless-subscriber-list-with-go-and-aws/"/><id>https://victoria.dev/archive/build-your-own-serverless-subscriber-list-with-go-and-aws/</id><author><name>Victoria Drake</name></author><published>2020-11-10T04:52:50-05:00</published><updated>2020-11-10T04:52:50-05:00</updated><content type="html"><![CDATA[<p>You can now subscribe to my email list on <a href="/">victoria.dev</a>! Here&rsquo;s how I lovingly built a subscription sign up flow with email confirmation that doesn&rsquo;t suck. You can too.</p>
<h2 id="introducing-simple-subscribe">Introducing Simple Subscribe</h2>
<p>If you&rsquo;re interested in managing your own mailing list or newsletter, you can set up Simple Subscribe on your own AWS resources to collect email addresses. This open source API is written in Go, and runs on AWS Lambda. Visitors to your site can sign up to your list, which is stored in a DynamoDB table, ready to be queried or exported at your leisure.</p>
<p>When someone signs up, they&rsquo;ll receive an email asking them to confirm their subscription. This is sometimes called &ldquo;double opt-in,&rdquo; although I prefer the term &ldquo;verified.&rdquo; Simple Subscribe works on serverless infrastructure and uses an AWS Lambda to handle subscription, confirmation, and unsubscribe requests.</p>
<p>You can find the <a href="https://github.com/victoriadrake/simple-subscribe">Simple Subscribe project, with its fully open-source code, on GitHub</a>. I encourage you to pull up the code and follow along! In this post I&rsquo;ll share each build step, the thought process behind the API&rsquo;s single-responsibility functions, and security considerations for an AWS project like this one.</p>
<h2 id="building-a-verified-subscription-flow">Building a verified subscription flow</h2>
<p>A non-verified email sign up process is straightforward. Someone puts their email into a box on your website, then that email goes into your database. However, if I&rsquo;ve taught you anything about <a href="/blog/sql-injection-and-xss-what-white-hat-hackers-know-about-trusting-user-input/">not trusting user input</a>, the very idea of a non-verified sign up process should raise your hackles. Spam may be great when fried in a sandwich, but no fun when it&rsquo;s running up your AWS bill.</p>
<p>While you can use a strategy like a CAPTCHA or puzzle for is-it-a-human verification, these can create enough friction to turn away your potential subscribers. Instead, a confirmation email can help to ensure both address correctness and user sentience.</p>
<p>To build a subscription flow with email confirmation, create single-responsibility functions that satisfy each logical step. Those are:</p>
<ol>
<li>Accept an email address and record it.</li>
<li>Generate a token associated with that email address and record it.</li>
<li>Send a confirmation email to that email address with the token.</li>
<li>Accept a verification request that has both the email address and token.</li>
</ol>
<p>To achieve each of these goals, Simple Subscribe uses the <a href="https://docs.aws.amazon.com/sdk-for-go/api/">official AWS SDK for Go</a> to interact with DynamoDB and SES.</p>
<p>At each stage, consider what the data looks like and how you store it. This can help to handle conundrums like, &ldquo;What happens if someone tries to subscribe twice?&rdquo; or even <a href="/blog/if-you-want-to-build-a-treehouse-start-at-the-bottom/">threat-modeling</a> such as, &ldquo;What if someone subscribes with an email they don&rsquo;t own?&rdquo;</p>
<p>Ready? Let&rsquo;s break down each step and see how the magic happens.</p>
<h3 id="subscribing">Subscribing</h3>
<p>The subscription process begins with a humble web form, like the one on my site&rsquo;s main page. A form input with attributes <code>type=&quot;email&quot; required</code> helps with validation, <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/email#Validation">thanks to the browser</a>. When submitted, the form sends a GET request to the Simple Subscribe subscription endpoint.</p>
<p>Simple Subscribe receives a GET request to this endpoint with a query string containing the intended subscriber&rsquo;s email. It then generates an <code>id</code> value and adds both <code>email</code> and <code>id</code> to your DynamoDB table.</p>
<p>The table item now looks like:</p>
<table>
<thead>
<tr>
<th>email</th>
<th>confirm</th>
<th>id</th>
<th>timestamp</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>subscriber@example.com</code></td>
<td><em>false</em></td>
<td><code>uuid-xxxxx</code></td>
<td>2020-11-01 00:27:39</td>
</tr>
</tbody>
</table>
<p>The <code>confirm</code> column, which holds a boolean, indicates that the item is a subscription request that has not yet been confirmed. To verify an email address in the database, you&rsquo;ll need to find the correct item and change <code>confirm</code> to <code>true</code>.</p>
<p>As you work with your data, consider the goal of each manipulation and how you might compare an incoming request to existing data.</p>
<p>For example, if someone made a subsequent subscription request for the same email address, how would you handle it? You might say, &ldquo;Create a new line item with a new <code>id</code>,&rdquo; however, this might not be best strategy when your serverless application database is paid for by request volume.</p>
<p>Since <a href="https://aws.amazon.com/dynamodb/pricing/">DynamoDB Pricing</a> depends on how much data you read and write to your tables, it&rsquo;s advantageous to avoid piling on excess data.</p>
<p>With that in mind, it would be prudent to handle subscription requests for the same email by performing an update instead of adding a new line. Simple Subscribe actually uses the same function to either add or update a database item. This is typically referred to as, &ldquo;update or insert.&rdquo;</p>
<p>In a database like SQLite this is accomplished with the <a href="https://www.sqlite.org/lang_UPSERT.html">UPSERT syntax</a>. In the case of DynamoDB, you use an update operation. For the <a href="https://docs.aws.amazon.com/sdk-for-go/api/service/dynamodb/">Go SDK</a>, its syntax is <code>UpdateItem</code>.</p>
<p>When a duplicate subscription request is received, the database item is matched on the <code>email</code> only. If an existing line item is found, its <code>id</code> and <code>timestamp</code> are overridden, which updates the existing database record and avoids flooding your table with duplicate requests.</p>
<h3 id="verifying-email-addresses">Verifying email addresses</h3>
<p>After submitting the form, the intended subscriber then receives an email from SES containing a link. This link is built using the <code>email</code> and <code>id</code> from the table, and takes the format:</p>
<pre tabindex="0"><code class="language-url" data-lang="url">&lt;BASE_URL&gt;&lt;VERIFY_PATH&gt;/?email=subscriber@example.com&amp;id=uuid-xxxxx
</code></pre><p>In this set up, the <code>id</code> is a UUID that acts as a secret token. It provides an identifier that you can match that is sufficiently complex and hard to guess. This approach deters people from subscribing with email addresses they don&rsquo;t control.</p>
<p>Visiting the link sends a request to your verification endpoint with the <code>email</code> and <code>id</code> in the query string. This time, it&rsquo;s important to compare both the incoming <code>email</code> and <code>id</code> values to the database record. This verifies that the recipient of the confirmation email is initiating the request.</p>
<p>The verification endpoint ensures that these values match an item in your database, then performs another update operation to set <code>confirm</code> to <code>true</code>, and update the timestamp. The item now looks like:</p>
<table>
<thead>
<tr>
<th>email</th>
<th>confirm</th>
<th>id</th>
<th>timestamp</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>subscriber@example.com</code></td>
<td><em>true</em></td>
<td><code>uuid-xxxxx</code></td>
<td>2020-11-01 00:37:39</td>
</tr>
</tbody>
</table>
<h3 id="querying-for-emails">Querying for emails</h3>
<p>You can now query your table to build your email list. Depending on your email sending solution, you might do this manually, with another Lambda, or even from the command line.</p>
<p>Since data for requested subscriptions (where <code>confirm</code> is <code>false</code>) is stored in the table alongside confirmed subscriptions, it&rsquo;s important to differentiate this data when querying for email addresses to send to. You&rsquo;ll want to ensure you only return emails where <code>confirm</code> is <code>true</code>.</p>
<h2 id="providing-unsubscribe-links">Providing unsubscribe links</h2>
<p>Similar to verifying an email address, Simple Subscribe uses <code>email</code> and <code>id</code> as arguments to the function that deletes an item from your DynamoDB table in order to unsubscribe an email address. To allow people to remove themselves from your list, you&rsquo;ll need to provide a URL in each email you send that includes their <code>email</code> and <code>id</code> as a query string to the unsubscribe endpoint. It would look something like:</p>
<pre tabindex="0"><code class="language-url" data-lang="url">&lt;BASE_URL&gt;&lt;UNSUBSCRIBE_PATH&gt;/?email=subscriber@example.com&amp;id=uuid-xxxxx
</code></pre><p>When the link is clicked, the query string is passed to the unsubscribe endpoint. If the provided <code>email</code> and <code>id</code> match a database item, that item will be deleted.</p>
<p>Proving a method for your subscribers to automatically remove themselves from your list, without any human intervention necessary, is part of an ethical and respectful philosophy towards handling the data that&rsquo;s been entrusted to you.</p>
<h2 id="caring-for-your-data">Caring for your data</h2>
<p>Once you decide to accept other people&rsquo;s data, it becomes your responsibility to care for it. This is applicable to everything you build. For Simple Subscribe, it means maintaining the security of your database, and periodically pruning your table.</p>
<p>In order to avoid retaining email addresses where <code>confirm</code> is <code>false</code> past a certain time frame, it would be a good idea to set up a cleaning function that runs on a regular schedule. This can be achieved manually, with an AWS Lambda function, or using the command line.</p>
<p>To clean up, find database items where <code>confirm</code> is <code>false</code> and <code>timestamp</code> is older than a particular point in time. Depending on your use case and request volumes, the frequency at which you choose to clean up will vary.</p>
<p>Also depending on your use case, you may wish to keep backups of your data. If you are particularly concerned about data integrity, you can explore <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/backuprestore_HowItWorks.html">On-Demand Backup</a> or <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/PointInTimeRecovery.html">Point-in-Time Recovery</a> for DynamoDB.</p>
<h2 id="build-your-independent-subscriber-base">Build your independent subscriber base</h2>
<p>Building your own subscriber list can be an empowering endeavor! Whether you intend to start a newsletter, send out notifications for new content, or want to create a community around your work, there&rsquo;s nothing more personal or direct than an email from me to you.</p>
<p>I encourage you to start building your subscriber base with Simple Subscribe today! Like most of my work, it&rsquo;s open source and free for your personal use. Dive into the code at <a href="https://github.com/victoriadrake/simple-subscribe">the GitHub repository</a>.</p>
]]></content></entry><entry><title type="html">WPA Key, WPA2, WPA3, and WEP Key: Wi-Fi security explained</title><link href="https://victoria.dev/archive/wpa-key-wpa2-wpa3-and-wep-key-wi-fi-security-explained/"/><id>https://victoria.dev/archive/wpa-key-wpa2-wpa3-and-wep-key-wi-fi-security-explained/</id><author><name>Victoria Drake</name></author><published>2020-10-19T04:02:27-04:00</published><updated>2020-10-19T04:02:27-04:00</updated><content type="html"><![CDATA[<p>Setting up new Wi-Fi? Picking the type of password you need can seem like an arbitrary choice. After all, WEP, WPA, WPA2, and WPA3 all have mostly the same letters in them. A password is a password, so what&rsquo;s the difference?</p>
<p>About 60 seconds to billions of years, as it turns out.</p>
<p>All Wi-Fi encryption is not created equal. Let&rsquo;s explore what makes these four acronyms so different, and how you can best protect your home and organization Wi-Fi.</p>
<h2 id="wired-equivalent-privacy-wep">Wired Equivalent Privacy (WEP)</h2>
<p>In the beginning, there was WEP.</p>
<figure><img src="/archive/wpa-key-wpa2-wpa3-and-wep-key-wi-fi-security-explained/wep.png"
    alt="cartoon of WEP letters"><figcaption>
      <p>Not to be confused with the name of a certain rap song.</p>
    </figcaption>
</figure>
<p><a href="https://en.wikipedia.org/wiki/Wired_Equivalent_Privacy">Wired Equivalent Privacy</a> is a deprecated security algorithm from 1997 that was intended to provide equivalent security to a wired connection. &ldquo;Deprecated&rdquo; means, &ldquo;Let&rsquo;s not do that anymore.&rdquo;</p>
<p>Even when it was first introduced, it was known not to be as strong as it could have been, for two reasons: one, its underlying encryption mechanism; and two, World War II.</p>
<p>During World War II, the impact of code breaking (or cryptanalysis) was <a href="https://en.wikipedia.org/wiki/History_of_cryptography#World_War_II_cryptography">huge</a>. Governments reacted by attempting to keep their best secret-sauce recipes at home. Around the time of WEP, <a href="https://en.wikipedia.org/wiki/Export_of_cryptography_from_the_United_States">U.S. Government restrictions on the export of cryptographic technology</a> caused access point manufacturers to limit their devices to 64-bit encryption. Though this was later lifted to 128-bit, even this form of encryption offered a very limited possible <a href="https://en.wikipedia.org/wiki/Key_size">key size</a>.</p>
<p>This proved problematic for WEP. The small key size resulted in being easier to <a href="https://en.wikipedia.org/wiki/Brute-force_attack">brute-force</a>, especially when that key doesn&rsquo;t often change.</p>
<p>WEP&rsquo;s underlying encryption mechanism is the <a href="https://en.wikipedia.org/wiki/RC4">RC4 stream cipher</a>. This cipher gained popularity due to its speed and simplicity, but that came at a cost. It&rsquo;s not the most robust algorithm. WEP employs a single shared key among its users that must be manually entered on an access point device. (When&rsquo;s the last time you changed your Wi-Fi password? Right.) WEP didn&rsquo;t help matters either by simply concatenating the key with the initialization vector &ndash; which is to say, it sort of mashed its secret-sauce bits together and hoped for the best.</p>
<blockquote>
<p><a href="https://en.wikipedia.org/wiki/Initialization_vector">Initialization Vector (IV)</a>: fixed-size input to a <a href="https://en.wikipedia.org/wiki/Cryptographic_primitive">low-level cryptographic algorithm</a>, usually random.</p>
</blockquote>
<p>Combined with the use of RC4, this left WEP particularly susceptible to <a href="https://en.wikipedia.org/wiki/Related-key_attack">related-key attack</a>. In the case of 128-bit WEP, your Wi-Fi password can be cracked by publicly-available tools in a matter of around <a href="https://eprint.iacr.org/2007/120">60 seconds</a> to <a href="https://www.networkcomputing.com/wireless-infrastructure/fbi-teaches-lesson-how-break-wi-fi-networks">three minutes</a>.</p>
<p>While some devices came to offer 152-bit or 256-bit WEP variants, this failed to solve the fundamental problems of WEP&rsquo;s underlying encryption mechanism.</p>
<p>So, yeah. Let&rsquo;s not do that anymore.</p>
<h2 id="wi-fi-protected-access-wpa">Wi-Fi Protected Access (WPA)</h2>
<p><img src="wpa.png" alt="WPA illustration"></p>
<p>A new, interim standard sought to temporarily &ldquo;patch&rdquo; the problem of WEP&rsquo;s (lack of) security. The name <a href="https://en.wikipedia.org/wiki/Wi-Fi_Protected_Access">Wi-Fi Protected Access (WPA)</a> certainly <em>sounds</em> more secure, so that&rsquo;s a good start; however, WPA first started out with another, more descriptive name.</p>
<p>Ratified in a <a href="https://en.wikipedia.org/wiki/IEEE_802.11i-2004">2004 IEEE standard</a>, <a href="https://en.wikipedia.org/wiki/Temporal_Key_Integrity_Protocol#Beck-Tews_attack">Temporal Key Integrity Protocol (TKIP)</a> uses a dynamically-generated, per-packet key. Each packet sent has a unique temporal 128-bit key, (See? Descriptive!) that solves the susceptibility to related-key attacks brought on by WEP&rsquo;s shared key mashing.</p>
<p>TKIP also implements other measures, such as a <a href="https://en.wikipedia.org/wiki/Message_authentication_code">message authentication code (MAC)</a>. Sometimes known as a checksum, a MAC provides a cryptographic way to verify that messages haven&rsquo;t been changed. In TKIP, an invalid MAC can also trigger rekeying of the session key. If the access point receives an invalid MAC twice within a minute, the attempted intrusion can be countered by changing the key an attacker is trying to crack.</p>
<p>Unfortunately, in order to preserve compatibility with the existing hardware that WPA was meant to &ldquo;patch,&rdquo; TKIP retained the use of the same underlying encryption mechanism as WEP &ndash; the RC4 stream cipher. While it certainly improved on the weaknesses of WEP, TKIP eventually proved vulnerable to new attacks that <a href="https://en.wikipedia.org/wiki/Temporal_Key_Integrity_Protocol#Security">extended previous attacks on WEP</a>. These attacks take a little longer to execute by comparison: for example, <a href="http://dl.aircrack-ng.org/breakingwepandwpa.pdf">twelve minutes</a> in the case of one, and <a href="https://www.rc4nomore.com/">52 hours</a> in another. This is more than sufficient, however, to deem TKIP no longer secure.</p>
<p>WPA, or TKIP, has since been deprecated as well. So let&rsquo;s also not do that anymore.</p>
<p>Which brings us to&hellip;</p>
<h2 id="wi-fi-protected-access-ii-wpa2">Wi-Fi Protected Access II (WPA2)</h2>
<p><img src="wpa2.png" alt="WPA2 illustration"></p>
<p>Rather than spend the effort to come up with an entirely new name, the improved <a href="https://en.wikipedia.org/wiki/Wi-Fi_Protected_Access#WPA2">Wi-Fi Protected Access II (WPA2)</a> standard instead focuses on using a new underlying cipher. Instead of the RC4 stream cipher, WPA2 employs a block cipher called <a href="https://en.wikipedia.org/wiki/Advanced_Encryption_Standard">Advanced Encryption Standard (AES)</a> to form the basis of its encryption protocol. The protocol itself, abbreviated <a href="https://en.wikipedia.org/wiki/CCMP_(cryptography)">CCMP</a>, draws most of its security from the length of its rather long name (I&rsquo;m kidding): Counter Mode Cipher Block Chaining Message Authentication Code Protocol, which shortens to Counter Mode CBC-MAC Protocol, or CCM mode Protocol, or CCMP. 🤷</p>
<p><a href="https://en.wikipedia.org/wiki/CCM_mode">CCM mode</a> is essentially a combination of a few good ideas. It provides data confidentiality through <a href="https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_.28CTR.29">CTR mode, or counter mode</a>. To vastly oversimplify, this adds complexity to plaintext data by encrypting the successive values of a count sequence that does not repeat. CCM also integrates <a href="https://en.wikipedia.org/wiki/CBC-MAC">CBC-MAC</a>, a block cipher method for constructing a MAC.</p>
<p>AES itself is on good footing. The AES specification was established in 2001 by the U.S. National Institute of Standards and Technology (NIST) after a five-year competitive selection process during which fifteen proposals for algorithm designs were evaluated. As a result of this process, a family of ciphers called Rijndael (Dutch) was selected, and a subset of these became AES. For the better part of two decades, AES has been used to protect every-day Internet traffic as well as <a href="https://en.wikipedia.org/wiki/Advanced_Encryption_Standard#Security">certain levels of classified information in the U.S. Government</a>.</p>
<p>While <a href="https://en.wikipedia.org/wiki/Advanced_Encryption_Standard#Known_attacks">possible attacks on AES</a> have been described, none have yet been proven to be practical in real-world use. The fastest <a href="https://web.archive.org/web/20141230025103/http://research.microsoft.com/en-us/projects/cryptanalysis/aesbc.pdf">attack on AES</a> in public knowledge is a <a href="https://en.wikipedia.org/wiki/Key-recovery_attack">key-recovery attack</a> that improved on brute-forcing AES by a factor of about four. How long would it take? Some <a href="https://web.archive.org/web/20150108165723/https://blog.agilebits.com/2011/08/18/aes-encryption-isnt-cracked/">billions of years</a>.</p>
<h2 id="wi-fi-protected-access-iii-wpa3">Wi-Fi Protected Access III (WPA3)</h2>
<p><img src="wpa3.png" alt="WPA3 illustration"></p>
<p>The next installment of the WPA trilogy has been required for new devices since July 1, 2020. Expected to further enhance the security of WPA2, the <a href="https://www.wi-fi.org/news-events/newsroom/wi-fi-alliance-introduces-wi-fi-certified-wpa3-security">WPA3 standard</a> seeks to improve password security by being more resilient to word list or <a href="https://en.wikipedia.org/wiki/Dictionary_attack">dictionary attacks</a>.</p>
<p>Unlike its predecessors, WPA3 will also offer <a href="https://en.wikipedia.org/wiki/Forward_secrecy">forward secrecy</a>. This adds the considerable benefit of protecting previously exchanged information even if a long-term secret key is compromised. Forward secrecy is already provided by protocols like TLS by using asymmetric keys to establish shared keys. You can learn <a href="/blog/what-is-tls-transport-layer-security-encryption-explained-in-plain-english/">more about TLS in this post</a>.</p>
<p>As WPA2 has not been deprecated, both WPA2 and WPA3 remain your top choices for Wi-Fi security.</p>
<h2 id="if-the-other-ones-suck-why-are-they-still-around">If the other ones suck, why are they still around?</h2>
<p>You may be wondering why your access point even allows you to choose an option other than WPA2 or WPA3. The likely reason is that you&rsquo;re using legacy hardware, which is what tech people call your mom&rsquo;s router.</p>
<p>Since the deprecation of WEP and WPA occurred (in old-people terms) rather recently, it&rsquo;s possible in large organizations as well as your parent&rsquo;s house to find older hardware that still uses these protocols. Even newer hardware may have a business need to support these older protocols.</p>
<p>While I may be able to convince you to invest in a shiny new top-of-the-line Wi-Fi appliance, most organizations are a different story. Unfortunately, many just aren&rsquo;t yet cognizant of the important role cybersecurity plays in meeting customer needs and boosting that bottom line. Additionally, switching to newer protocols may require new internal hardware or firmware upgrades. Especially on complex systems in large organizations, upgrading devices can be financially or strategically difficult.</p>
<h2 id="boost-your-wi-fi-security">Boost your Wi-Fi security</h2>
<p>If it&rsquo;s an option, choose WPA2 or WPA3. Cybersecurity is a field that evolves by the day, and getting stuck in the past can have dire consequences.</p>
<p>If you can&rsquo;t use WPA2 or WPA3, do the best you can to take additional security measures. The best bang for your buck is to use a Virtual Private Network (VPN). Using a VPN is a good idea no matter which type of Wi-Fi encryption you have. On open Wi-Fi (coffee shops) and using WEP, it&rsquo;s plain irresponsible to go without a VPN. Kind of like shouting out your bank details as you order your second cappuccino.</p>
<figure><img src="/archive/wpa-key-wpa2-wpa3-and-wep-key-wi-fi-security-explained/cafewifi.png"
    alt="A cartoon of shouting out your bank details at a coffeeshop.">
</figure>
<p>When possible, ensure you only connect to known networks that you or your organization control. Many cybersecurity attacks are executed when victims connect to an imitation public Wi-Fi access point, also called an evil twin attack, or Wi-Fi phishing. These fake hotspots are easily created using publicly accessible programs and tools. A reputable VPN can help mitigate damage from these attacks as well, but it&rsquo;s always better not to take the risk. If you travel often, consider purchasing a portable hotspot that uses a cellular data plan, or using data SIM cards for all your devices.</p>
<h2 id="much-more-than-just-acronyms">Much more than just acronyms</h2>
<p>WEP, WPA, WPA2, and WPA3 mean a lot more than a bunch of similar letters &ndash; in some cases, it&rsquo;s a difference of billions of years minus about 60 seconds.</p>
<p>On more of a now-ish timescale, I hope I&rsquo;ve taught you something new about the security of your Wi-Fi and how you can improve it!</p>
]]></content></entry><entry><title type="html">Your cybersecurity starter pack</title><link href="https://victoria.dev/archive/your-cybersecurity-starter-pack/"/><id>https://victoria.dev/archive/your-cybersecurity-starter-pack/</id><author><name>Victoria Drake</name></author><published>2020-10-04T04:30:12-04:00</published><updated>2020-10-04T04:30:12-04:00</updated><content type="html"><![CDATA[<p>Readers of my blog typically know more about technology and cybersecurity than most people. This article is for most people. If someone you know could benefit from a simple and straightforward introduction to cybersecurity tools, please share this article with them &ndash; it benefits everyone!</p>
<p>If you&rsquo;ve ever said to yourself:</p>
<p><em>&ldquo;There&rsquo;s no one targeting lil ol&rsquo; me.&rdquo;</em><br>
<em>&ldquo;I have nothing to hide, anyway.&rdquo;</em><br>
<em>&ldquo;I&rsquo;m too busy to learn all this stuff. Why can&rsquo;t someone just give me a simple summary of best practices that I can skim in approximately seven minutes?&rdquo;</em></p>
<p>First of all, you might want to stop talking to yourself in public. Secondly, here is a simple summary of best practices that you can skim in approximately seven minutes.</p>
<h2 id="introducing-your-three-step-starter-pack">Introducing your three-step starter pack</h2>
<p>While there are many different degrees of security, privacy, and anonymity, these three basics are accessible to all:</p>
<ol>
<li>Use a VPN</li>
<li>Use multifactor authentication</li>
<li>Develop a healthy sense of skepticism</li>
</ol>
<p>I&rsquo;ll discuss each of these and help you get started with your security upgrade. But first&hellip;</p>
<h2 id="why-is-cybersecurity-important">Why is cybersecurity important?</h2>
<p>Would you let just anyone walk into your house, or even look through your open doorway from across the street? If not, you might appreciate that the cybersecurity practices we&rsquo;ll discuss today are not that different from locking your front door.</p>
<p>Cybersecurity isn&rsquo;t about finding some magic spell that completely secures your online activities &ndash; that would be nice, but it&rsquo;s unrealistic. Good security practices are about employing some thoughtful habits that make your online activities more secure than the next guy, in much the same way as you learned to lock your front door.</p>
<p>Security breaches and incidents happen every day. Most of them occur because an automated scanner cast a wide net and found a person or company with lax security that a hacker could then exploit. Don&rsquo;t be that guy.</p>
<h2 id="1-use-a-vpn">1. Use a VPN</h2>
<p>Let&rsquo;s say you send a lot of mail, but never bother to put your letters in envelopes or even fold them in half. Anyone who bothers to look can read all your dirty secrets (not that you have any).</p>
<p>When you use a Virtual Private Network, or VPN, especially if you often connect to public WiFi, it&rsquo;s like putting your letters into cryptographically-sealed envelopes and sending them via a special invisible courier service. No one but the intended recipient can read your letters, and no one but you and the courier know to whom the letters are sent.</p>
<figure><img src="/archive/your-cybersecurity-starter-pack/vpnmail.png"
    alt="An illustration of a locked envelope"><figcaption>
      <p>Encrypted mail still won&rsquo;t stop you from the accidental <em>reply all</em>, unfortunately.</p>
    </figcaption>
</figure>

<p>VPNs prevent others from reading your communications. This may include opportunistic attackers who scan open WiFi, and even your own internet service provider (ISP) who may sell your usage data for advertising dollars.</p>
<h3 id="choosing-a-vpn">Choosing a VPN</h3>
<p>A few important differentiating factors can help you choose a VPN provider.</p>
<ol>
<li>
<p><strong>Is it free?</strong> VPNs cost money to operate; if one is offered for free, consider what they might be doing in order to cover their costs. Generally, I recommend avoiding free VPN apps and services; they&rsquo;ll typically cost you much more than you&rsquo;ll know. Expect to pay between $5-$10 USD monthly for the service.</p>
</li>
<li>
<p><strong>Where is it based?</strong> Understand where your VPN provider is based, and what that country&rsquo;s laws allow them to do with your data.</p>
</li>
<li>
<p><strong>Do they keep logs?</strong> Part of the philosophy of using a VPN is that no one has any business getting into your business when it comes to online activities. When a VPN provider keeps logs of your usage, that defeats the purpose. Instead of your ISP knowing just what you&rsquo;re up to online, that knowledge is simply transferred to the logging VPN. Look for VPN providers with a strict no-logging policy, or if you&rsquo;re up for it, <a href="/blog/set-up-a-pi-hole-vpn-on-an-aws-lightsail-instance/">roll your own</a>.</p>
</li>
</ol>
<h2 id="2-use-multifactor-authentication">2. Use multifactor authentication</h2>
<p>Passwords are dead. Computationally, they are a solved problem. Cracking your password is just <a href="https://howsecureismypassword.net/">a matter of time</a>.</p>
<p>Unfortunately, many people still help to speed up the process by using the same <a href="https://haveibeenpwned.com/Passwords">compromised passwords</a> for multiple accounts, putting themselves at further risk.</p>
<p>The answer, at least for now, is <a href="https://en.wikipedia.org/wiki/Multi-factor_authentication">multifactor authentication</a> (MFA). MFA is made up of three kinds of authentication factors:</p>
<ol>
<li>Something you know, like a pass phrase;</li>
<li>Something you have, like a chip pin card or phone; and</li>
<li>Something that you are, like your face or fingerprint.</li>
</ol>
<figure><img src="/archive/your-cybersecurity-starter-pack/mfa.png"
    alt="An illustration of the letters MFA"><figcaption>
      <p>Also the name of my next beatboxing team.</p>
    </figcaption>
</figure>

<p>Two or more of these factors are infinitely better than a password alone, especially if <a href="https://en.wikipedia.org/wiki/List_of_the_most_common_passwords">your password is on this list</a>.</p>
<p>Multiple authentication factors are now widely supported by account providers and social media sites. If you have the choice, avoid using text messages, or SMS, as a way of receiving authentication codes. SMS authentication leaves you vulnerable to the <a href="https://en.wikipedia.org/wiki/SIM_swap_scam">SIM swap attack</a> - please direct further questions to <a href="https://www.nytimes.com/2019/09/05/technology/sim-swap-jack-dorsey-hack.html">Jack Dorsey</a>.</p>
<p>Instead, use a One Time Password (OTP) app such as <a href="https://authy.com/">Authy</a> to generate codes on your device. This ensures that you alone, using that particular device, will have the correct authentication code.</p>
<p>You can also use hardware authentication keys such as the <a href="https://www.yubico.com/">YubiKey</a>, but these aren&rsquo;t yet as widely supported as OTP apps.</p>
<h2 id="3-develop-a-healthy-sense-of-skepticism">3. Develop a healthy sense of skepticism</h2>
<p>Social engineering, sometimes SE, is the use of psychological persuasion to get an unwitting target to give up access or information. This can take the form of phishing emails, letters, or phone calls (vishing) as well as far more sophisticated spear-phishing attacks of high-value targets, like company executives.</p>
<p>While some attacks are easier to spot, others <a href="https://www.youtube.com/watch?v=8bAuA1isCz0">use cognitive biases very effectively</a> and are difficult even for security professionals to avoid. No human is immune.</p>
<p>Ultimately, the weakest link in your cybersecurity defense is you. All the VPNs and MFA on the Internet won&rsquo;t protect you if a scam can trick you into opening the front gates. Always look a Trojan gift horse in the mouth.</p>
<figure><img src="/archive/your-cybersecurity-starter-pack/horse.png"
    alt="An illustration of a Trojan horse"><figcaption>
      <p>Yes, I know it&rsquo;s a very nice looking wooden horse. Also free. Did you order it? No? Then it can stay outside.</p>
    </figcaption>
</figure>

<p>Develop the habit of second-guessing things delivered to your virtual doorstep. Email, phone, and messaging scams range in sophistication. Even security professionals can fall for a good scam.</p>
<p>One way to protect yourself is to practice a healthy sense of skepticism. Question communications that ask you to click on links or visit a website, even if they come from someone you know or a company you use.</p>
<p>If you&rsquo;re not certain that your bank or mother sent this email, pick up the phone and call them. Even if you think you are certain, pick up the phone and double check. You don&rsquo;t call your mother enough, anyway.</p>
<p>Oh, and if the person on the phone is from your local tax office or the IRS or the CRA and they&rsquo;re about to freeze your accounts because a case of mistaken identity has resulted in you being criminally charged for not repaying a loan on a 600-foot yacht in Malibu, just hang up. You know better than that. Tax agencies don&rsquo;t have phones.</p>
<h2 id="a-safer-internet">A safer Internet</h2>
<p>Congratulations! You now have three tools to make your personal cybersecurity better than the next guy&rsquo;s. If enough people do that, the whole neighborhood (or in this case, the Internet) will benefit as a result.</p>
<p>If this article piqued your interest, you can go further and <a href="/blog/outsourcing-security-with-1password-authy-and-privacy.com/">outsource your security with a password manager and temporary virtual credit cards</a>.</p>
<h3 id="cheat-sheets-and-other-resources">Cheat sheets and other resources</h3>
<p>I&rsquo;ll leave you with a few resources that I&rsquo;ve enjoyed:</p>
<ul>
<li>The Electronic Frontier Foundation website <a href="https://ssd.eff.org/">Surveillance Self Defense</a> offers many great guides and how-to&rsquo;s, such as setting up the encrypted messaging app <a href="https://www.signal.org/">Signal</a> on your mobile device, and protecting yourself on social media.</li>
<li>The Cybersecurity and Infrastructure Security Agency (CISA) offers many <a href="https://www.cisa.gov/resources-tools/all-resources-tools">shareable starter resources</a>.</li>
<li>Working from home? The National Security Agency Central Security Service has <a href="https://www.nsa.gov/Press-Room/Telework-and-Mobile-Security-Guidance/">Telework and Mobile Security Guides</a> that discuss best practices for an unprecedented era of remote work.</li>
</ul>
]]></content></entry><entry><title type="html">Increase developer confidence with a great Django test suite</title><link href="https://victoria.dev/archive/increase-developer-confidence-with-a-great-django-test-suite/"/><id>https://victoria.dev/archive/increase-developer-confidence-with-a-great-django-test-suite/</id><author><name>Victoria Drake</name></author><published>2020-10-01T05:50:37-04:00</published><updated>2020-10-01T05:50:37-04:00</updated><content type="html"><![CDATA[<p>Done correctly, tests are one of your application&rsquo;s most valuable assets.</p>
<p>The Django framework in particular offers your team the opportunity to create an efficient testing practice. Based on the Python standard library <code>unittest</code>, proper tests in Django are fast to write, faster to run, and can offer you a seamless continuous integration solution for taking the pulse of your developing application.</p>
<p>With comprehensive tests, developers have higher confidence when pushing changes. I&rsquo;ve seen firsthand in my own teams that good tests can boost development velocity as a direct result of a better developer experience.</p>
<p>In this article, I&rsquo;ll share my own experiences in building useful tests for Django applications, from the basics to the best possible execution.</p>
<h2 id="what-to-test">What to test</h2>
<p>Tests are extremely important. Far beyond simply letting you know if a function works, tests can form the basis of your team&rsquo;s understanding of how your application is <em>intended</em> to work.</p>
<p>Here&rsquo;s the main goal: if you hit your head and forgot everything about how your application works tomorrow, you should be able to regain most of your understanding by reading and running the tests you write today.</p>
<p>Here are some questions that may be helpful to ask as you decide what to test:</p>
<ul>
<li>What is our customer supposed to be able to do?</li>
<li>What is our customer <em>not</em> supposed to be able to do?</li>
<li>What should this method, view, or logical flow achieve?</li>
<li>When, how, or where is this feature supposed to execute?</li>
</ul>
<p>Tests that make sense for your application can help build developer confidence. With these sensible safeguards in place, developers make improvements more readily, and feel confident introducing innovative solutions to product needs. The result is an application that comes together faster, and features that are shipped often and with confidence.</p>
<p><img src="pbj-tests.png" alt="A cartoon for peanut butter and jelly sandwich tests"></p>
<h2 id="where-to-put-tests">Where to put tests</h2>
<p>If you only have a few tests, you may organize your test files similarly to <a href="https://docs.djangoproject.com/en/3.1/ref/django-admin/#startapp">Django&rsquo;s default app template</a> by putting them all in a file called <code>tests.py</code>. This straightforward approach is best for smaller applications.</p>
<p>As your application grows, you may like to split your tests into different files, or test modules. One method is to use a directory to organize your files, such as <code>projectroot/app/tests/</code>. The name of each test file within that directory should begin with <code>test</code>, for example, <code>test_models.py</code>.</p>
<p>Besides being aptly named, Django will find these files using <a href="https://docs.python.org/3/library/unittest.html#unittest-test-discovery">built-in test discovery</a> based on the <code>unittest</code> module. All files in your application with names that begin with <code>test</code> will be collected into a test suite.</p>
<p>This convenient test discovery allows you to place test files anywhere that makes sense for your application. As long as they&rsquo;re correctly named, Django&rsquo;s test utility can find and run them.</p>
<h2 id="how-to-document-a-test">How to document a test</h2>
<p>Use <a href="https://www.python.org/dev/peps/pep-0257/">docstrings</a> to explain what a test is intended to verify at a high level. For example:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">test_create_user</span>(self):
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;&#34;&#34;Creating a new user object should also create an associated profile object&#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># ...</span>
</span></span></code></pre></div><p>These docstrings help you quickly understand what a test is supposed to be doing. Besides navigating the codebase, this helps to make it obvious when a test doesn&rsquo;t verify what the docstring says it should.</p>
<p>Docstrings are also shown when the tests are being run, which can be helpful for logging and debugging.</p>
<h2 id="what-a-test-needs-to-work">What a test needs to work</h2>
<p>Django tests can be quickly set up using data created in the <a href="https://docs.djangoproject.com/en/3.1/topics/testing/tools/#django.test.TestCase.setUpTestData"><code>setUpTestData()</code> method</a>. You can use various approaches to create your test data, such as utilizing external files, or even hard-coding silly phrases or the names of your staff. Personally, I much prefer to use a fake-data-generation library, such as <a href="https://github.com/joke2k/faker/"><code>faker</code></a>.</p>
<p>The proper set up of arbitrary testing data can help you ensure that you&rsquo;re testing your application functionality instead of accidentally testing test data. Because generators like <code>faker</code> add some degree of unexpectedness to your inputs, it can be more representative of real-world use.</p>
<p>Here is an example set up for a test:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> django.test <span style="color:#f92672">import</span> TestCase
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> faker <span style="color:#f92672">import</span> Faker
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> app.models <span style="color:#f92672">import</span> MyModel, AnotherModel
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>fake <span style="color:#f92672">=</span> Faker()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">MyModelTest</span>(TestCase):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">setUpTestData</span>(cls):
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;&#34;&#34;Quickly set up data for the whole TestCase&#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>        cls<span style="color:#f92672">.</span>user_first <span style="color:#f92672">=</span> fake<span style="color:#f92672">.</span>first_name()
</span></span><span style="display:flex;"><span>        cls<span style="color:#f92672">.</span>user_last <span style="color:#f92672">=</span> fake<span style="color:#f92672">.</span>last_name()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">test_create_models</span>(self):
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;&#34;&#34;Creating a MyModel object should also create AnotherModel object&#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># In test methods, use the variables created above</span>
</span></span><span style="display:flex;"><span>        test_object <span style="color:#f92672">=</span> MyModel<span style="color:#f92672">.</span>objects<span style="color:#f92672">.</span>create(
</span></span><span style="display:flex;"><span>            first_name<span style="color:#f92672">=</span>self<span style="color:#f92672">.</span>user_first,
</span></span><span style="display:flex;"><span>            last_name<span style="color:#f92672">=</span>self<span style="color:#f92672">.</span>user_last,
</span></span><span style="display:flex;"><span>            <span style="color:#75715e"># ...</span>
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>        another_model <span style="color:#f92672">=</span> AnotherModel<span style="color:#f92672">.</span>objects<span style="color:#f92672">.</span>get(my_model<span style="color:#f92672">=</span>test_object)
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>assertEqual(another_model<span style="color:#f92672">.</span>first_name, self<span style="color:#f92672">.</span>user_first)
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># ...</span>
</span></span></code></pre></div><p>Tests pass or fail based on the outcome of the assertion methods. You can use <a href="https://docs.python.org/3/library/unittest.html#assert-methods">Python&rsquo;s <code>unittest</code> methods</a>, and <a href="https://docs.djangoproject.com/en/3.1/topics/testing/tools/#assertions">Django&rsquo;s assertion methods</a>.</p>
<p>For further guidance on writing tests, see <a href="https://docs.djangoproject.com/en/3.1/topics/testing/">Testing in Django</a>.</p>
<h2 id="best-possible-execution-for-running-your-tests">Best possible execution for running your tests</h2>
<p>Django&rsquo;s test suite is manually run with:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>./manage.py test
</span></span></code></pre></div><p>I rarely run my Django tests this way.</p>
<p>The best, or most efficient, testing practice is one that occurs without you or your developers ever thinking, &ldquo;I need to run the tests first.&rdquo; The beauty of Django&rsquo;s near-effortless test suite set up is that it can be seamlessly run as a part of regular developer activities. This could be in a pre-commit hook, or in a continuous integration or deployment workflow.</p>
<p>I&rsquo;ve previously written about how to use pre-commit hooks to <a href="/posts/technical-ergonomics-for-the-efficient-developer/">improve your developer ergonomics</a> and save your team some brainpower. Django&rsquo;s speedy tests can be run this way, and they become especially efficient if you can <a href="https://docs.djangoproject.com/en/3.1/ref/django-admin/#cmdoption-test-parallel">run tests in parallel</a>.</p>
<p>Tests that run as part of a CI/CD workflow, for example, <a href="/posts/django-project-best-practices-to-keep-your-developers-happy/#continuous-testing-with-github-actions">on pull requests with GitHub Actions</a>, require no regular effort from your developers to remember to run tests at all. I&rsquo;m not sure how plainly I can put it &ndash; this one&rsquo;s literally a no-brainer.</p>
<h2 id="testing-your-way-to-a-great-django-application">Testing your way to a great Django application</h2>
<p>Tests are extremely important, and underappreciated. They can catch logical errors in your application. They can help explain and validate how concepts and features of your product actually function. Best of all, tests can boost developer confidence and development velocity as a result.</p>
<p>The best tests are ones that are relevant, help to explain and define your application, and are run continuously without a second thought. I hope I&rsquo;ve now shown you how testing in Django can help you to achieve these goals for your team!</p>
]]></content></entry><entry><title type="html">Delightful Django Development: Setup, Hooks, and CI/CD</title><link href="https://victoria.dev/archive/delightful-django-development-setup-hooks-and-ci/cd/"/><id>https://victoria.dev/archive/delightful-django-development-setup-hooks-and-ci/cd/</id><author><name>Victoria Drake</name></author><published>2020-09-22T04:55:19-04:00</published><updated>2020-09-22T04:55:19-04:00</updated><content type="html"><![CDATA[<p>Do you want your team to <em>enjoy</em> your development workflow? Do you think building software should be <em>fun and existentially fulfilling?</em> If so, <em>this is the post</em> for you!</p>
<p>I&rsquo;ve been developing with Django for years, and I&rsquo;ve never been happier with my Django project set up than I am right now. Here&rsquo;s how I&rsquo;m making a day of developing with Django the most relaxing and enjoyable development experience possible for myself and my engineering team.</p>
<h2 id="a-custom-cli-tool-for-your-django-project">A custom CLI tool for your Django project</h2>
<p>Instead of typing:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>python3 -m venv env
</span></span><span style="display:flex;"><span>source env/bin/activate
</span></span><span style="display:flex;"><span>pip install -r requirements.txt
</span></span><span style="display:flex;"><span>python3 manage.py makemigrations
</span></span><span style="display:flex;"><span>python3 manage.py migrate
</span></span><span style="display:flex;"><span>python3 manage.py collectstatic
</span></span><span style="display:flex;"><span>python3 manage.py runserver
</span></span></code></pre></div><p>Wouldn&rsquo;t it be much nicer to type:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>make start
</span></span></code></pre></div><p>&hellip;and have all that happen for you? I think so!</p>
<p>We can do that with a self-documenting Makefile! Here&rsquo;s one I frequently use when developing my Django applications, like <a href="https://applybyapi.com/">ApplyByAPI.com</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-Makefile" data-lang="Makefile"><span style="display:flex;"><span>VENV <span style="color:#f92672">:=</span> env
</span></span><span style="display:flex;"><span>BIN <span style="color:#f92672">:=</span> <span style="color:#66d9ef">$(</span>VENV<span style="color:#66d9ef">)</span>/bin
</span></span><span style="display:flex;"><span>PYTHON <span style="color:#f92672">:=</span> <span style="color:#66d9ef">$(</span>BIN<span style="color:#66d9ef">)</span>/python
</span></span><span style="display:flex;"><span>SHELL <span style="color:#f92672">:=</span> /bin/bash
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">include</span> <span style="color:#960050;background-color:#1e0010">.env</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">.PHONY</span><span style="color:#f92672">:</span> help
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">help</span><span style="color:#f92672">:</span> <span style="color:#75715e">## Show this help
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    @egrep -h <span style="color:#e6db74">&#39;\s##\s&#39;</span> <span style="color:#66d9ef">$(</span>MAKEFILE_LIST<span style="color:#66d9ef">)</span> | awk <span style="color:#e6db74">&#39;BEGIN {FS = &#34;:.*?## &#34;}; {printf &#34;\033[36m%-20s\033[0m %s\n&#34;, $$1, $$2}&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">.PHONY</span><span style="color:#f92672">:</span> venv
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">venv</span><span style="color:#f92672">:</span> <span style="color:#75715e">## Make a new virtual environment
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    python3 -m venv <span style="color:#66d9ef">$(</span>VENV<span style="color:#66d9ef">)</span> <span style="color:#f92672">&amp;&amp;</span> source <span style="color:#66d9ef">$(</span>BIN<span style="color:#66d9ef">)</span>/activate
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">.PHONY</span><span style="color:#f92672">:</span> install
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">install</span><span style="color:#f92672">:</span> venv <span style="color:#75715e">## Make venv and install requirements
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">$(</span>BIN<span style="color:#66d9ef">)</span>/pip install --upgrade -r requirements.txt
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">freeze</span><span style="color:#f92672">:</span> <span style="color:#75715e">## Pin current dependencies
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">$(</span>BIN<span style="color:#66d9ef">)</span>/pip freeze &gt; requirements.txt
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">migrate</span><span style="color:#f92672">:</span> <span style="color:#75715e">## Make and run migrations
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">$(</span>PYTHON<span style="color:#66d9ef">)</span> manage.py makemigrations
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">$(</span>PYTHON<span style="color:#66d9ef">)</span> manage.py migrate
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">db-up</span><span style="color:#f92672">:</span> <span style="color:#75715e">## Pull and start the Docker Postgres container in the background
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    docker pull postgres
</span></span><span style="display:flex;"><span>    docker-compose up -d
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">db-shell</span><span style="color:#f92672">:</span> <span style="color:#75715e">## Access the Postgres Docker database interactively with psql. Pass in DBNAME=&lt;name&gt;.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    docker exec -it container_name psql -d <span style="color:#66d9ef">$(</span>DBNAME<span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">.PHONY</span><span style="color:#f92672">:</span> test
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">test</span><span style="color:#f92672">:</span> <span style="color:#75715e">## Run tests
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">$(</span>PYTHON<span style="color:#66d9ef">)</span> manage.py test application --verbosity<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span> --parallel --failfast
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">.PHONY</span><span style="color:#f92672">:</span> run
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">run</span><span style="color:#f92672">:</span> <span style="color:#75715e">## Run the Django server
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">$(</span>PYTHON<span style="color:#66d9ef">)</span> manage.py runserver
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">start</span><span style="color:#f92672">:</span> install migrate run <span style="color:#75715e">## Install requirements, apply migrations, then start development server
</span></span></span></code></pre></div><p>You&rsquo;ll notice the presence of the line <code>include .env</code> above. This ensures <code>make</code> has access to environment variables stored in a file called <code>.env</code>. This allows Make to utilize these variables in its commands, for example, the name of my virtual environment, or to pass in <code>$(DBNAME)</code> to <code>psql</code>.</p>
<p>What&rsquo;s with that weird &ldquo;<code>##</code>&rdquo; comment syntax? A Makefile like this gives you a handy suite of command-line aliases you can check in to your Django project. It&rsquo;s very useful so long as you&rsquo;re able to remember what all those aliases are.</p>
<p>The <code>help</code> command above, which runs by default, prints a helpful list of available commands when you run <code>make</code> or <code>make help</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>help                 Show this help
</span></span><span style="display:flex;"><span>venv                 Make a new virtual environment
</span></span><span style="display:flex;"><span>install              Make venv and install requirements
</span></span><span style="display:flex;"><span>migrate              Make and run migrations
</span></span><span style="display:flex;"><span>db-up                Pull and start the Docker Postgres container in the background
</span></span><span style="display:flex;"><span>db-shell             Access the Postgres Docker database interactively with psql
</span></span><span style="display:flex;"><span>test                 Run tests
</span></span><span style="display:flex;"><span>run                  Run the Django server
</span></span><span style="display:flex;"><span>start                Install requirements, apply migrations, then start development server
</span></span></code></pre></div><p>All the usual Django commands are covered, and we&rsquo;ve got a <code>test</code> command that runs our tests with the options we prefer. Brilliant.</p>
<p>You can read my full <a href="/posts/how-to-create-a-self-documenting-makefile/">post about self-documenting Makefiles here</a>, which also includes an example Makefile using <code>pipenv</code>.</p>
<h2 id="save-your-brainpower-with-pre-commit-hooks">Save your brainpower with pre-commit hooks</h2>
<p>I previously wrote about some <a href="/posts/technical-ergonomics-for-the-efficient-developer/">technical ergonomics</a> that can make it a lot easier for teams to develop great software.</p>
<p>One area that&rsquo;s a no-brainer is using pre-commit hooks to lint code prior to checking it in. This helps to ensure the quality of the code your developers check in, but most importantly, ensures that no one on your team is spending time trying to remember if it should be single or double quotes or where to put a line break.</p>
<p>The confusingly-named <a href="https://pre-commit.com/">pre-commit framework</a> is an otherwise fantastic way to keep hooks (which are not included in cloned repositories) consistent across local environments.</p>
<p>Here is my configuration file, <code>.pre-commit-config.yaml</code>, for my Django projects:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">fail_fast</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">repos</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">repo</span>: <span style="color:#ae81ff">https://github.com/pre-commit/pre-commit-hooks</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">rev</span>: <span style="color:#ae81ff">v3.1.0</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">hooks</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">id</span>: <span style="color:#ae81ff">detect-aws-credentials</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">repo</span>: <span style="color:#ae81ff">https://github.com/psf/black</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">rev</span>: <span style="color:#ae81ff">19.</span><span style="color:#ae81ff">3b0</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">hooks</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">id</span>: <span style="color:#ae81ff">black</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">repo</span>: <span style="color:#ae81ff">https://github.com/asottile/blacken-docs</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">rev</span>: <span style="color:#ae81ff">v1.7.0</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">hooks</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">id</span>: <span style="color:#ae81ff">blacken-docs</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">additional_dependencies</span>: [<span style="color:#ae81ff">black==19.3b0]</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">repo</span>: <span style="color:#ae81ff">local</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">hooks</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">id</span>: <span style="color:#ae81ff">markdownlint</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">name</span>: <span style="color:#ae81ff">markdownlint</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">description</span>: <span style="color:#e6db74">&#34;Lint Markdown files&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">entry</span>: <span style="color:#ae81ff">markdownlint &#39;**/*.md&#39; --fix --ignore node_modules --config &#34;./.markdownlint.json&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">language</span>: <span style="color:#ae81ff">node</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">types</span>: [<span style="color:#ae81ff">markdown]</span>
</span></span></code></pre></div><p>These hooks check for accidental secret commits, format Python files using <a href="https://github.com/psf/black">Black</a>, format Python snippets in Markdown files using <a href="https://github.com/asottile/blacken-docs"><code>blacken-docs</code></a>, and <a href="https://github.com/igorshubovych/markdownlint-cli">lint Markdown files</a> as well. To install them, just type <code>pre-commit install</code>.</p>
<p>There are likely even more useful hooks available for your particular use case: see <a href="https://pre-commit.com/hooks.html">supported hooks</a> to explore.</p>
<h2 id="useful-gitignores">Useful gitignores</h2>
<p>An underappreciated way to improve your team&rsquo;s daily development experience is to make sure your project uses a well-rounded <code>.gitignore</code> file. It can help prevent files containing secrets from being committed, and can additionally save developers hours of tedium by ensuring you&rsquo;re never sifting through a <code>git diff</code> of generated files.</p>
<p>To efficiently create a <a href="https://www.toptal.com/developers/gitignore/api/python,django">gitignore for Python and Django projects</a>, Toptal&rsquo;s <a href="https://gitignore.io">gitignore.io</a> can be a nice resource for generating a robust <code>.gitignore</code> file.</p>
<p>I still recommend examining the generated results yourself to ensure that ignored files suit your use case, and that nothing you want ignored is commented out.</p>
<h2 id="continuous-testing-with-github-actions">Continuous testing with GitHub Actions</h2>
<p>If your team works on GitHub, setting up a testing process with Actions is low-hanging fruit.</p>
<p>Tests that run in a consistent environment on every pull request can help eliminate &ldquo;works on my machine&rdquo; conundrums, as well as ensure no one&rsquo;s sitting around waiting for a test to run locally.</p>
<p>A hosted CI environment like GitHub Actions can also help when running integration tests that require using managed services resources. You can use <a href="https://docs.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets">encrypted secrets in a repository</a> to grant the Actions runner access to resources in a testing environment, without worrying about creating testing resources and access keys for each of your developers to use.</p>
<p>I&rsquo;ve written on many occasions about setting up Actions workflows, including <a href="/posts/a-lightweight-tool-agnostic-ci/cd-flow-with-github-actions/">using one to run your Makefile</a>, and <a href="/blog/publishing-github-event-data-with-github-actions-and-pages/">how to integrate GitHub event data</a>. GitHub even <a href="https://github.blog/2020-06-26-github-action-hero-victoria-drake/">interviewed me about Actions</a> once.</p>
<p>For Django projects, here&rsquo;s a GitHub Actions workflow that runs tests with a consistent Python version whenever someone opens a pull request in the repository.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">name</span>: <span style="color:#ae81ff">Run Django tests</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">on</span>: <span style="color:#ae81ff">pull_request</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">jobs</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">test</span>:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">runs-on</span>: <span style="color:#ae81ff">ubuntu-latest</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">steps</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/checkout@v2</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Set up Python</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/setup-python@v2</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">with</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">python-version</span>: <span style="color:#e6db74">&#39;3.8&#39;</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Install dependencies</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">run</span>: <span style="color:#ae81ff">make install</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Run tests</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">run</span>: <span style="color:#ae81ff">make test</span>
</span></span></code></pre></div><p>For the installation and test commands, I&rsquo;ve simply utilized the <a href="#a-custom-cli-tool-for-your-django-project">Makefile</a> that&rsquo;s been checked in to the repository. A benefit of using your Makefile commands in your CI test workflows is that you only need to keep them updated in one place &ndash; your Makefile! No more &ldquo;why is this working locally but not in CI??!?&rdquo; headaches.</p>
<p>If you want to step up your security game, you can add <a href="https://github.com/victoriadrake/django-security-check">Django Security Check</a> as an Action too.</p>
<h2 id="set-up-your-django-project-for-success">Set up your Django project for success</h2>
<p>Want to help keep your development team happy? Set them up for success with these best practices for Django development. Remember, an ounce of brainpower is worth a pound of software!</p>
]]></content></entry><entry><title type="html">Manipulating data with Django migrations</title><link href="https://victoria.dev/archive/manipulating-data-with-django-migrations/"/><id>https://victoria.dev/archive/manipulating-data-with-django-migrations/</id><author><name>Victoria Drake</name></author><published>2020-09-14T02:12:57-04:00</published><updated>2020-09-14T02:12:57-04:00</updated><content type="html"><![CDATA[<p>Growing, successful applications are a lovely problem to have. As a product develops, it tends to accumulate complication the way your weekend cake project accumulates layers of frosting. Thankfully, Django, my favorite batteries-included framework, handles complexity pretty well.</p>
<p>Django <a href="/posts/writing-efficient-django/#django-models">models help humans work with data in a way that makes sense to our brains</a>, and the framework offers plenty of classes you can inherit to help you rapidly develop a robust application from scratch. As for developing on existing Django applications, there&rsquo;s a feature for that, too. In this article, we&rsquo;ll cover how to use Django migrations to update your existing models and database.</p>
<h2 id="whats-under-the-hood">What&rsquo;s under the hood</h2>
<p>Django migrations are Python files that help you add and change things in your database tables to reflect changes in your Django models. To understand how Django migrations help you work with data, it may be helpful to understand the underlying structures we&rsquo;re working with.</p>
<h3 id="whats-a-database-table">What&rsquo;s a database table</h3>
<p>If you&rsquo;ve laid eyes on a spreadsheet before, you&rsquo;re already most of the way to understanding a database table. In a relational database, for example, a PostgreSQL database, you can expect to see data organized into columns and rows. A relational database table may have a set number of columns and any number of rows.</p>
<p>In Django, each model is its own table. For example, here&rsquo;s a Django model:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> django.db <span style="color:#f92672">import</span> models
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Lunch</span>(models<span style="color:#f92672">.</span>Model):
</span></span><span style="display:flex;"><span>    left_side <span style="color:#f92672">=</span> models<span style="color:#f92672">.</span>CharField(max_length<span style="color:#f92672">=</span><span style="color:#ae81ff">100</span>, null<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span><span style="display:flex;"><span>    center <span style="color:#f92672">=</span> models<span style="color:#f92672">.</span>CharField(max_length<span style="color:#f92672">=</span><span style="color:#ae81ff">100</span>, null<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span><span style="display:flex;"><span>    right_side <span style="color:#f92672">=</span> models<span style="color:#f92672">.</span>CharField(max_length<span style="color:#f92672">=</span><span style="color:#ae81ff">100</span>, null<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span></code></pre></div><p>Each field is a column, and each row is a Django object instance of that model. Here&rsquo;s a representation of a database table for the Django model &ldquo;Lunch&rdquo; above. In the database, its name would be <code>lunch_table</code>.</p>
<table>
<thead>
<tr>
<th>id</th>
<th>left_side</th>
<th>center</th>
<th>right_side</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Fork</td>
<td>Plate</td>
<td>Spoon</td>
</tr>
</tbody>
</table>
<p>The model <code>Lunch</code> has three fields: <code>left_side</code>, <code>center</code>, and <code>right-side</code>. One instance of a <code>Lunch</code> object would have &ldquo;Fork&rdquo; for the <code>left_side</code>, a &ldquo;Plate&rdquo; for the <code>center</code>, and &ldquo;Spoon&rdquo; for the <code>right_side</code>. Django <a href="https://docs.djangoproject.com/en/3.2/topics/db/models/#automatic-primary-key-fields">automatically adds an <code>id</code> field</a> if you don&rsquo;t specify a primary key.</p>
<p>If you wanted to change the name of your Lunch model, you would do so in your <code>models.py</code> code. For example, change &ldquo;Lunch&rdquo; to &ldquo;Dinner,&rdquo; then <a href="https://docs.djangoproject.com/en/3.2/ref/django-admin/#makemigrations">run <code>python manage.py makemigrations</code></a>. You&rsquo;ll see:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>python manage.py makemigrations
</span></span><span style="display:flex;"><span>Did you rename the backend.Lunch model to Dinner? [y/N] y
</span></span><span style="display:flex;"><span>Migrations for &#39;backend&#39;:
</span></span><span style="display:flex;"><span>  backend/migrations/0003_auto_20200922_2331.py
</span></span><span style="display:flex;"><span>    - Rename model Lunch to Dinner
</span></span></code></pre></div><p>Django automatically generates the appropriate migration files. The relevant line of the generated migrations file in this case would look like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>migrations<span style="color:#f92672">.</span>RenameModel(old_name<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Lunch&#34;</span>, new_name<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Dinner&#34;</span>),
</span></span></code></pre></div><p>This operation would rename our &ldquo;Lunch&rdquo; model to &ldquo;Dinner&rdquo; while keeping everything else the same. But what if you also wanted to change the structure of the database table itself, its schema, as well as make sure that existing data ends up in the right place on your Dinner table?</p>
<p>Let&rsquo;s explore how to turn our Lunch model into a Dinner model that looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> django.db <span style="color:#f92672">import</span> models
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Dinner</span>(models<span style="color:#f92672">.</span>Model):
</span></span><span style="display:flex;"><span>    top_left <span style="color:#f92672">=</span> models<span style="color:#f92672">.</span>CharField(max_length<span style="color:#f92672">=</span><span style="color:#ae81ff">100</span>, null<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span><span style="display:flex;"><span>    top_center <span style="color:#f92672">=</span> models<span style="color:#f92672">.</span>CharField(max_length<span style="color:#f92672">=</span><span style="color:#ae81ff">100</span>, null<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span><span style="display:flex;"><span>    top_right <span style="color:#f92672">=</span> models<span style="color:#f92672">.</span>CharField(max_length<span style="color:#f92672">=</span><span style="color:#ae81ff">100</span>, null<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span><span style="display:flex;"><span>    bottom_left <span style="color:#f92672">=</span> models<span style="color:#f92672">.</span>CharField(max_length<span style="color:#f92672">=</span><span style="color:#ae81ff">100</span>, null<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span><span style="display:flex;"><span>    bottom_center <span style="color:#f92672">=</span> models<span style="color:#f92672">.</span>CharField(max_length<span style="color:#f92672">=</span><span style="color:#ae81ff">100</span>, null<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span><span style="display:flex;"><span>    bottom_right <span style="color:#f92672">=</span> models<span style="color:#f92672">.</span>CharField(max_length<span style="color:#f92672">=</span><span style="color:#ae81ff">100</span>, null<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span></code></pre></div><p>&hellip;with a database table that would look like this:</p>
<table>
<thead>
<tr>
<th>id</th>
<th>top_left</th>
<th>top_center</th>
<th>top_right</th>
<th>bottom_left</th>
<th>bottom_center</th>
<th>bottom_right</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Bread plate</td>
<td>Spoon</td>
<td>Glass</td>
<td>Fork</td>
<td>Plate</td>
<td>Knife</td>
</tr>
</tbody>
</table>
<h2 id="manipulating-data-with-django-migrations">Manipulating data with Django migrations</h2>
<p>Before you begin to manipulate your data, it&rsquo;s always a good idea to create a backup of your database that you can restore in case something goes wrong. There are various ways to do this depending on the database you&rsquo;re using. You can typically find instructions by searching for <code>&lt;your database name&gt;</code> and keywords like <code>backup</code>, <code>recovery</code>, or <code>snapshot</code>.</p>
<p>In order to design your migration, it&rsquo;s helpful to become familiar with the available <a href="https://docs.djangoproject.com/en/3.2/ref/migration-operations/">migration operations</a>. Migrations are run step-by-step, and each operation is some flavor of adding, removing, or altering data. Like a strategic puzzle, it&rsquo;s important to make model changes one step at a time so that the generated migrations have the correct result.</p>
<p>We&rsquo;ve already renamed our model successfully. Now, we&rsquo;ll rename the fields that hold the data we want to retain:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Dinner</span>(models<span style="color:#f92672">.</span>Model):
</span></span><span style="display:flex;"><span>    bottom_left <span style="color:#f92672">=</span> models<span style="color:#f92672">.</span>CharField(max_length<span style="color:#f92672">=</span><span style="color:#ae81ff">100</span>, null<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span><span style="display:flex;"><span>    bottom_center <span style="color:#f92672">=</span> models<span style="color:#f92672">.</span>CharField(max_length<span style="color:#f92672">=</span><span style="color:#ae81ff">100</span>, null<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span><span style="display:flex;"><span>    top_center <span style="color:#f92672">=</span> models<span style="color:#f92672">.</span>CharField(max_length<span style="color:#f92672">=</span><span style="color:#ae81ff">100</span>, null<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span></code></pre></div><p>Django is sometimes smart enough to determine the old and new field names correctly. You&rsquo;ll be asked for confirmation:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>python manage.py makemigrations
</span></span><span style="display:flex;"><span>Did you rename dinner.center to dinner.bottom_center (a CharField)? [y/N] y
</span></span><span style="display:flex;"><span>Did you rename dinner.left_side to dinner.bottom_left (a CharField)? [y/N] y
</span></span><span style="display:flex;"><span>Did you rename dinner.right_side to dinner.top_center (a CharField)? [y/N] y
</span></span><span style="display:flex;"><span>Migrations for &#39;backend&#39;:
</span></span><span style="display:flex;"><span>  backend/migrations/0004_auto_20200914_2345.py
</span></span><span style="display:flex;"><span>    - Rename field center on dinner to bottom_center
</span></span><span style="display:flex;"><span>    - Rename field left_side on dinner to bottom_left
</span></span><span style="display:flex;"><span>    - Rename field right_side on dinner to top_center
</span></span></code></pre></div><p>In some cases, you&rsquo;ll want to try renaming the field and running <code>makemigrations</code> one at a time.</p>
<p>Now that the existing fields have been migrated to their new names, add the remaining fields to the model:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Dinner</span>(models<span style="color:#f92672">.</span>Model):
</span></span><span style="display:flex;"><span>    top_left <span style="color:#f92672">=</span> models<span style="color:#f92672">.</span>CharField(max_length<span style="color:#f92672">=</span><span style="color:#ae81ff">100</span>, null<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span><span style="display:flex;"><span>    top_center <span style="color:#f92672">=</span> models<span style="color:#f92672">.</span>CharField(max_length<span style="color:#f92672">=</span><span style="color:#ae81ff">100</span>, null<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span><span style="display:flex;"><span>    top_right <span style="color:#f92672">=</span> models<span style="color:#f92672">.</span>CharField(max_length<span style="color:#f92672">=</span><span style="color:#ae81ff">100</span>, null<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span><span style="display:flex;"><span>    bottom_left <span style="color:#f92672">=</span> models<span style="color:#f92672">.</span>CharField(max_length<span style="color:#f92672">=</span><span style="color:#ae81ff">100</span>, null<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span><span style="display:flex;"><span>    bottom_center <span style="color:#f92672">=</span> models<span style="color:#f92672">.</span>CharField(max_length<span style="color:#f92672">=</span><span style="color:#ae81ff">100</span>, null<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span><span style="display:flex;"><span>    bottom_right <span style="color:#f92672">=</span> models<span style="color:#f92672">.</span>CharField(max_length<span style="color:#f92672">=</span><span style="color:#ae81ff">100</span>, null<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span></code></pre></div><p>Running <code>makemigrations</code> again now gives us:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>python manage.py makemigrations
</span></span><span style="display:flex;"><span>Migrations for &#39;backend&#39;:
</span></span><span style="display:flex;"><span>  backend/migrations/0005_auto_20200914_2351.py
</span></span><span style="display:flex;"><span>    - Add field bottom_right to dinner
</span></span><span style="display:flex;"><span>    - Add field top_left to dinner
</span></span><span style="display:flex;"><span>    - Add field top_right to dinner
</span></span></code></pre></div><p>You&rsquo;re done! By generating Django migrations, you&rsquo;ve successfully set up your <code>dinner_table</code> and moved existing data to its new spot.</p>
<h2 id="additional-complexity">Additional complexity</h2>
<p>You&rsquo;ll notice that our Lunch and Dinner models are not very complex. Out of Django&rsquo;s many <a href="https://docs.djangoproject.com/en/3.2/ref/models/fields/#field-types">model field options</a>, we&rsquo;re just using <code>CharField</code>. We also set <code>null=True</code> to let Django store empty values as <code>NULL</code> in the database.</p>
<p>Django migrations can handle additional complexity, such as changing field types, and whether a blank or null value is permitted. I keep Django&rsquo;s <a href="https://docs.djangoproject.com/en/3.2/ref/models/fields/#">model field reference</a> handy as I work with varying types of data and different use cases.</p>
<h2 id="de-mystified-migrations">De-mystified migrations</h2>
<p>I hope this article has helped you better understand Django migrations and how they work!</p>
<p>Now that you can change models and manipulate existing data in your Django application, be sure to use your powers wisely! Backup your database, research and plan your migrations, and always run tests before working with customer data. By doing so, you have the potential to enable your application to grow &ndash; with manageable levels of complexity.</p>
]]></content></entry><entry><title type="html">What is TLS? Transport Layer Security encryption explained in plain english</title><link href="https://victoria.dev/archive/what-is-tls-transport-layer-security-encryption-explained-in-plain-english/"/><id>https://victoria.dev/archive/what-is-tls-transport-layer-security-encryption-explained-in-plain-english/</id><author><name>Victoria Drake</name></author><published>2020-09-05T04:48:39-06:00</published><updated>2020-09-05T04:48:39-06:00</updated><content type="html"><![CDATA[<p>If you want to have a confidential conversation with someone you know, you might meet up in person and find a private place to talk. If you want to send data confidentially over the Internet, you might have a few more considerations to cover.</p>
<p>TLS, or Transport Layer Security, refers to a protocol. &ldquo;Protocol&rdquo; is a word that means, &ldquo;the way we&rsquo;ve agreed to do things around here,&rdquo; more or less. The &ldquo;transport layer&rdquo; part of TLS simply refers to host-to-host communication, such as how a client and a server interact, in the <a href="https://en.wikipedia.org/wiki/Internet_protocol_suite">Internet protocol suite model</a>.</p>
<p>The TLS protocol attempts to solve these fundamental problems:</p>
<ul>
<li>How do I know you are who you say you are?</li>
<li>How do I know this message from you hasn&rsquo;t been tampered with?</li>
<li>How can we communicate securely?</li>
</ul>
<p>Here&rsquo;s how TLS works, explained in plain English. As with many successful interactions, it begins with a handshake.</p>
<h2 id="getting-to-know-you">Getting to know you</h2>
<p>The basic process of a <a href="https://en.wikipedia.org/wiki/Transport_Layer_Security#TLS_handshake">TLS handshake</a> involves a client, such as your web browser, and a server, such as one hosting a website, establishing some ground rules for communication. It begins with the client saying hello. Literally. It&rsquo;s called a <em>ClientHello</em> message.</p>
<p>The <em>ClientHello</em> message tells the server which TLS protocol version and <em>cipher suites</em> it supports. While &ldquo;cipher suite&rdquo; sounds like a fancy hotel upgrade, it just refers to a set of algorithms that can be used to secure communications. The server, in a similarly named <em>ServerHello</em> message, chooses the protocol version and cipher suite to use from the choices offered. Other data may also be sent, for example, a <em>session ID</em> if the server supports resuming a previous handshake.</p>
<p><img src="hello-hello.png" alt="A cartoon of a client and server saying hello"></p>
<p>Depending on the cipher suite chosen, the client and server exchange further information in order to establish a shared secret. Often, this process moves the exchange from <a href="https://en.wikipedia.org/wiki/Public-key_cryptography">asymmetric cryptography</a> to <a href="https://en.wikipedia.org/wiki/Symmetric-key_algorithm">symmetric cryptography</a> with varying levels of complexity. Let&rsquo;s explore these concepts at a general level and see why they matter to TLS.</p>
<h2 id="asymmetric-beginnings">Asymmetric beginnings</h2>
<p>This is asymmetry:</p>
<figure><img src="/archive/what-is-tls-transport-layer-security-encryption-explained-in-plain-english/asymmetry-eggs.jpeg"
    alt="A small egg and a big egg"><figcaption>
      <p>Small egg, big egg.</p>
    </figcaption>
</figure>

<p>Asymmetric cryptography is one method by which you can perform <em>authentication</em>. When you authenticate yourself, you answer the fundamental question, &ldquo;How do I know you are who you say you are?&rdquo;</p>
<p>In an asymmetric cryptographic system, you use a pair of keys in order to achieve authentication. These keys are asymmetric. One key is your public key, which, as you would guess, is public. The other is your private key, which &ndash; well, you know.</p>
<p>Typically, during the TLS handshake, the server will provide its public key via its digital certificate, sometimes still called its <em>SSL certificate</em>, though TLS replaces the deprecated Secure Sockets Layer (SSL) protocol. Digital certificates are provided and verified by trusted third parties known as <a href="https://en.wikipedia.org/wiki/Certificate_authority">Certificate Authorities (CA)</a>, which are a whole other article in themselves.</p>
<p>While anyone may encrypt a message using your public key, only your private key can then decrypt that message. The security of asymmetric cryptography relies only on your private key staying private, hence the asymmetry. It&rsquo;s also asymmetric in the sense that it&rsquo;s a one-way trip. Alice can send messages encrypted with your public key to you, but neither of your keys will help you send an encrypted message to Alice.</p>
<h2 id="symmetric-secrets">Symmetric secrets</h2>
<p>Asymmetric cryptography also requires more computational resources than symmetric cryptography. Thus when a TLS handshake begins with an asymmetric exchange, the client and server will use this initial communication to establish a shared secret, sometimes called a <em>session key</em>. This key is symmetric, meaning that both parties use the same shared secret and must maintain that secrecy for the encryption to be secure.</p>
<figure><img src="/archive/what-is-tls-transport-layer-security-encryption-explained-in-plain-english/asym-vs-sym.png"
    alt="A cartoon of public-key cryptography vs. shared-key cryptography"><figcaption>
      <p>Wise man say: share your public key, but keep your shared keys private.</p>
    </figcaption>
</figure>

<p>By using the initial asymmetric communication to establish a session key, the client and server can rely on the session key being known only to them. For the rest of the session, they&rsquo;ll both use this same shared key to encrypt and decrypt messages, which speeds up communication.</p>
<h2 id="secure-sessions">Secure sessions</h2>
<p>A TLS handshake may use asymmetric cryptography or other cipher suites to establish the shared session key. Once the session key is established, the handshaking portion is complete and the session begins.</p>
<p>The <em>session</em> is the duration of encrypted communication between the client and server. During this time, messages are encrypted and decrypted using the session key that only the client and server have. This ensures that communication is secure.</p>
<p>The integrity of exchanged information is maintained by using a checksum. Messages exchanged using session keys have a <a href="https://en.wikipedia.org/wiki/Message_authentication_code">message authentication code (MAC)</a> attached. This is not the same thing as your device&rsquo;s <a href="https://en.wikipedia.org/wiki/MAC_address">MAC address</a>. The MAC is generated and verified using the session key. Because of this, either party can detect if a message has been changed before being received. This solves the fundamental question, &ldquo;How do I know this message from you hasn&rsquo;t been tampered with?&rdquo;</p>
<p>Sessions can end deliberately, due to network disconnection, or from the client staying idle for too long. Once a session ends, it must be re-established via a new handshake or through previously established secrets called <em>session IDs</em> that allow resuming a session.</p>
<h2 id="tls-and-you">TLS and you</h2>
<p>Let&rsquo;s recap:</p>
<ul>
<li>TLS is a cryptographic protocol for providing secure communication.</li>
<li>The process of creating a secure connection begins with a handshake.</li>
<li>The handshake establishes a shared session key that is then used to secure messages and provide message integrity.</li>
<li>Sessions are temporary, and once ended, must be re-established or resumed.</li>
</ul>
<p>This is just a surface-level skim of the very complex cryptographic systems that help to keep your communications secure. For more depth on the topic, I recommend exploring cipher suites and the various <a href="https://en.wikipedia.org/wiki/Cipher_suite#Supported_algorithms">supported algorithms</a>.</p>
<p>The TLS protocol serves a very important purpose in your everyday life. It helps to secure your emails to family, your online banking activities, and the connection by which you&rsquo;re reading this article. The <a href="https://en.wikipedia.org/wiki/HTTPS">HTTPS communication protocol</a> is encrypted using TLS. Every time you see that little lock icon in your URL bar, you&rsquo;re experiencing firsthand all the concepts you&rsquo;ve just read about in this article. Now you know the answer to the last question: &ldquo;How can we communicate securely?&rdquo;</p>
]]></content></entry><entry><title type="html">Deceptively simple search-and-replace across multiple files</title><link href="https://victoria.dev/archive/deceptively-simple-search-and-replace-across-multiple-files/"/><id>https://victoria.dev/archive/deceptively-simple-search-and-replace-across-multiple-files/</id><author><name>Victoria Drake</name></author><published>2020-08-25T04:48:39-06:00</published><updated>2020-08-25T04:48:39-06:00</updated><content type="html"><![CDATA[<p>While a multitude of methods exist to search for and replace words in a single file, what do you do when you&rsquo;ve got a string to update across multiple unrelated files, all with different names? You harness the power of command line tools, of course!</p>
<p>First, you&rsquo;ll need to <code>find</code> all the files you want to change. Stringing together what are effectively search queries for <code>find</code> is really only limited by your imagination. Here&rsquo;s a simple example that finds Python files:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>find . -name <span style="color:#e6db74">&#39;*.py&#39;</span>
</span></span></code></pre></div><p>The <code>-name</code> test searches for a pattern, such as all files ending in <code>.py</code>, but <code>find</code> can do a lot more with other test conditions, including <code>-regex</code> tests. Run <code>find --help</code> to see the multitude of options.</p>
<p>Further tune your search by using <code>grep</code> to get only the files that contain the string you want to change, such as by adding:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>grep -le <span style="color:#e6db74">&#39;\&lt;a whale\&gt;&#39;</span>
</span></span></code></pre></div><p>The <code>-l</code> option gives you just the file names for all files containing a pattern (denoted with <code>-e</code>) that match &ldquo;a whale&rdquo;.</p>
<p>Using Vim&rsquo;s impressive <code>:bufdo</code> lets you run the same command across multiple buffers, interactively working with all of these files without the tedium of opening, saving, and closing each file, one at a time.</p>
<p>Let&rsquo;s plug your powerful <code>find</code>+<code>grep</code> results into Vim with:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>vim <span style="color:#e6db74">`</span>find . -name <span style="color:#e6db74">&#39;*.py&#39;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>-exec grep -le <span style="color:#e6db74">&#39;\&lt;a whale\&gt;&#39;</span> <span style="color:#f92672">{}</span> <span style="color:#ae81ff">\;</span><span style="color:#e6db74">`</span>
</span></span></code></pre></div><p>Using backtick-expansion to pass our search to Vim opens up multiple buffers ready to go. (Do <code>:h backtick-expansion</code> in Vim for more.) Now you can apply the Vim command <code>:bufdo</code> to all of these files and perform actions such as interactive search-and-replace:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-vim" data-lang="vim"><span style="display:flex;"><span>:<span style="color:#a6e22e">bufdo</span> %<span style="color:#a6e22e">s</span><span style="color:#e6db74">/a whale/</span><span style="color:#a6e22e">a</span> <span style="color:#a6e22e">bowl</span> <span style="color:#a6e22e">of</span> <span style="color:#a6e22e">petunias</span>/<span style="color:#a6e22e">gce</span>
</span></span></code></pre></div><p>The <code>g</code> for &ldquo;global&rdquo; will change occurrences of the pattern on all lines. The <code>e</code> will omit errors if the pattern is not found. The <code>c</code> option makes this interactive; if you&rsquo;re feeling confident, you can omit it to make the changes without reviewing each one.</p>
<p>If one of the patterns contains a <code>/</code> character, you can substitute the separator in the above command to make it more readable. Vim will assume the character following the <code>%s</code> is the separator, so for example:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-vim" data-lang="vim"><span style="display:flex;"><span>:<span style="color:#a6e22e">bufdo</span> %<span style="color:#a6e22e">s_a</span> <span style="color:#a6e22e">whale_a</span> <span style="color:#a6e22e">bowl</span> <span style="color:#a6e22e">of</span> <span style="color:#a6e22e">peonies</span>/<span style="color:#a6e22e">petunias_gce</span>
</span></span></code></pre></div><p>When you&rsquo;ve finished going through all the buffers, save all the work you&rsquo;ve completed with:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-vim" data-lang="vim"><span style="display:flex;"><span>:<span style="color:#a6e22e">bufdo</span> <span style="color:#a6e22e">wq</span>!
</span></span></code></pre></div><p>Then bask in the glory of your saved time and effort.</p>
]]></content></entry><entry><title type="html">How GitHub Codespaces increases productivity and lowers barriers</title><link href="https://victoria.dev/archive/how-github-codespaces-increases-productivity-and-lowers-barriers/"/><id>https://victoria.dev/archive/how-github-codespaces-increases-productivity-and-lowers-barriers/</id><author><name>Victoria Drake</name></author><published>2020-08-15T16:08:08-04:00</published><updated>2020-08-15T16:08:08-04:00</updated><content type="html"><![CDATA[<p>The most recent integration between Visual Studio Code and GitHub can help make development accessible and welcoming: Codespaces in GitHub!</p>
<p>Now in beta, <a href="https://docs.github.com/en/github/developing-online-with-codespaces/about-codespaces">GitHub Codespaces</a> provide an online, in-the-browser IDE powered by Visual Studio Code. This lets you use this full-featured IDE, complete with extensions, terminal, Git commands, and all the settings you&rsquo;re accustomed to, on any machine. You can now bring your development workflow anywhere using a tablet or other browser-based device.</p>
<p>Codespaces is great news for open source contributors, too. <a href="https://docs.github.com/en/github/developing-online-with-codespaces/configuring-codespaces-for-your-project">Adding a codespace configuration</a> to your project is a great way to invite new folks to easily start contributing.</p>
<p>A new open source contributor or new hire at your organization can quickly fire up a codespace and get hacking on a <code>good first issue</code> with no local environment set up or installations necessary!</p>
<p><img src="open-with-codespaces-button.png" alt="Starting a new codespace"></p>
<p>We&rsquo;ve added codespace configuration settings over at the <a href="https://github.com/OWASP/wstg">OWASP Web Security Testing Guide (WSTG)</a>. Want to take it for a spin? See our <a href="https://github.com/OWASP/wstg/issues">open issues</a>.</p>
<h2 id="configuring-codespaces">Configuring Codespaces</h2>
<p>You can use Visual Studio Code&rsquo;s <code>.devcontainer</code> folder to configure a development container for your repository as well.</p>
<p>Many <a href="https://github.com/microsoft/vscode-dev-containers/tree/master/containers">pre-built containers are available</a> &ndash; just copy the <code>.devcontainer</code> you need to your repository root. If your repository doesn&rsquo;t have one, a <a href="https://github.com/microsoft/vscode-dev-containers/tree/master/containers/codespaces-linux">default base Linux image</a> will be used.</p>
<p>Here&rsquo;s a reason to remove <code>.vscode</code> from your <code>.gitignore</code> file. Any new codespaces created in your repository will now respect settings found at <code>.vscode/settings.json</code>. This means that your online IDE can have the same Workspace configuration as you have on your local machine. Isn&rsquo;t that useful!</p>
<h2 id="making-codespaces-personal">Making Codespaces personal</h2>
<p>For next-level <a href="https://docs.github.com/en/github/developing-online-with-codespaces/personalizing-codespaces-for-your-account">dotfiles personalization</a>, consider committing relevant files from your local <code>dotfiles</code> folder as a public GitHub repository at <code>yourusername/dotfiles</code>.</p>
<p>When you create a new codespace, this brings in your configurations, such as shell aliases and preferences, by creating symlinks to dotfiles in your codespace <code>$HOME</code>. This personalizes all the codespaces you create in your account.</p>
<p>Need some inspiration? Browse <a href="https://github.com/victoriadrake/dotfiles">my dotfiles repository on GitHub</a>.</p>
<p><a href="https://docs.github.com/en/github/developing-online-with-codespaces/developing-in-a-codespace">Developing in a codespace</a> is a familiar experience for Visual Studio Code users, right down to running an application locally.</p>
<p>Thanks to <a href="https://docs.github.com/en/github/developing-online-with-codespaces/developing-in-a-codespace">port forwarding</a>, when I run an application in a codespace terminal, clicking on the resulting <code>localhost</code> URL takes me to the appropriate port as output from my codespace.</p>
<p>When I&rsquo;m working on this website in my codespace, for example, I run <code>hugo serve</code> then click the provided <code>localhost:1313</code> link to see a preview of my changes in another browser tab.</p>
<p>Want to stay in sync between devices? There&rsquo;s an extension for that. You can <a href="https://docs.github.com/en/github/developing-online-with-codespaces/connecting-to-your-codespace-from-visual-studio-code">connect to your codespace from Visual Studio Code</a> on your local machine so you can always pick up right where you left off.</p>
<h2 id="develop-anywhere">Develop anywhere</h2>
<p>Codespaces is a super exciting addition to my GitHub workflow. It allows me to access my full development process pretty much anywhere, using devices like my iPad.</p>
<p>It&rsquo;ll also make it easier for new open source contributors or new hires at your organization to hit the ground running with a set-up IDE. If you have access to the limited beta, I invite you to spin up a codespace and try <a href="https://github.com/OWASP/wstg/issues">contributing to the WSTG</a>, or to <a href="https://github.com/victoriadrake?tab=repositories">an issue on one of my open source projects</a>.</p>
<p>I&rsquo;m looking forward to general availability and seeing what the open source community will dream up for GitHub Codespaces next!</p>
<p>And yes &ndash; codespaces support <a href="https://github.com/victoriadrake/kabukicho-vscode">your favorite Visual Studio Code theme</a>. 😈</p>
<figure class="screenshot"><img src="/archive/how-github-codespaces-increases-productivity-and-lowers-barriers/codespace.png"
    alt="A screenshot of my codespace"><figcaption>
      <p>Screenshot of a codespace with the Kabukichō theme for Visual Studio Code</p>
    </figcaption>
</figure>

]]></content></entry><entry><title type="html">How to create a self-documenting Makefile</title><link href="https://victoria.dev/archive/how-to-create-a-self-documenting-makefile/"/><id>https://victoria.dev/archive/how-to-create-a-self-documenting-makefile/</id><author><name>Victoria Drake</name></author><published>2020-08-05T08:55:19-04:00</published><updated>2020-08-05T08:55:19-04:00</updated><content type="html"><![CDATA[<p>My new favorite way to completely underuse a Makefile? Creating personalized, per-project repository workflow command aliases that you can check in.</p>
<p>Can a Makefile improve your DevOps and keep developers happy? How awesome would it be if a new developer working on your project didn&rsquo;t start out by copying and pasting commands from your README? What if instead of:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>pip3 install pipenv
</span></span><span style="display:flex;"><span>pipenv shell --python 3.8
</span></span><span style="display:flex;"><span>pipenv install --dev
</span></span><span style="display:flex;"><span>npm install
</span></span><span style="display:flex;"><span>pre-commit install --install-hooks
</span></span><span style="display:flex;"><span><span style="color:#75715e"># look up how to install Framework X...</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># copy and paste from README...</span>
</span></span><span style="display:flex;"><span>npm run serve
</span></span></code></pre></div><p>&hellip; you could just type:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>make start
</span></span></code></pre></div><p>&hellip;and then start working?</p>
<h2 id="making-a-difference">Making a difference</h2>
<p>I use <code>make</code> every day to take the tedium out of common development activities like updating programs, installing dependencies, and testing. To do all this with a Makefile (GNU make), we use <a href="https://www.gnu.org/software/make/manual/make.html#Rules">Makefile rules</a> and <a href="https://www.gnu.org/software/make/manual/make.html#Recipes">recipes</a>. Similar parallels exist for POSIX flavor make, like <a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/make.html#tag_20_76_13_04">Target Rules</a>; here&rsquo;s a <a href="https://nullprogram.com/blog/2017/08/20/">great article</a> on POSIX-compatible Makefiles.</p>
<p>Here&rsquo;s some examples of things we can <code>make</code> easier (sorry):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-Makefile" data-lang="Makefile"><span style="display:flex;"><span><span style="color:#a6e22e">update</span><span style="color:#f92672">:</span> <span style="color:#75715e">## Do apt upgrade and autoremove
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    sudo apt update <span style="color:#f92672">&amp;&amp;</span> sudo apt upgrade -y
</span></span><span style="display:flex;"><span>    sudo apt autoremove -y
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">env</span><span style="color:#f92672">:</span>
</span></span><span style="display:flex;"><span>    pip3 install pipenv
</span></span><span style="display:flex;"><span>    pipenv shell --python 3.8
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">install</span><span style="color:#f92672">:</span> <span style="color:#75715e">## Install or update dependencies
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    pipenv install --dev
</span></span><span style="display:flex;"><span>    npm install
</span></span><span style="display:flex;"><span>    pre-commit install --install-hooks
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">serve</span><span style="color:#f92672">:</span> <span style="color:#75715e">## Run the local development server
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    hugo serve --enableGitInfo --disableFastRender --environment development
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">initial</span><span style="color:#f92672">:</span> update env install serve <span style="color:#75715e">## Install tools and start development server
</span></span></span></code></pre></div><p>Now we have some command-line aliases that you can check in! Great idea! If you&rsquo;re wondering what&rsquo;s up with that weird <code>##</code> comment syntax, it gets better.</p>
<h2 id="a-self-documenting-makefile">A self-documenting Makefile</h2>
<p>Aliases are great, if you remember what they all are and what they do without constantly typing <code>cat Makefile</code>. Naturally, you need a <code>help</code> command:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-Makefile" data-lang="Makefile"><span style="display:flex;"><span><span style="color:#a6e22e">.PHONY</span><span style="color:#f92672">:</span> help
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">help</span><span style="color:#f92672">:</span> <span style="color:#75715e">## Show this help
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    @egrep -h <span style="color:#e6db74">&#39;\s##\s&#39;</span> <span style="color:#66d9ef">$(</span>MAKEFILE_LIST<span style="color:#66d9ef">)</span> | sort | awk <span style="color:#e6db74">&#39;BEGIN {FS = &#34;:.*?## &#34;}; {printf &#34;\033[36m%-20s\033[0m %s\n&#34;, $$1, $$2}&#39;</span>
</span></span></code></pre></div><p>With a little command-line magic, this <code>egrep</code> command takes the output of <code>MAKEFILE_LIST</code>, sorts it, and uses <code>awk</code> to find strings that follow the <code>##</code> pattern. It then prints a helpful formatted version of the comments.</p>
<p>We&rsquo;ll put it at the top of the file so it&rsquo;s the default target. Now to see all our handy shortcuts and what they do, we just run <code>make</code>, or <code>make help</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>help                 Show this help
</span></span><span style="display:flex;"><span>initial              Install tools and start development server
</span></span><span style="display:flex;"><span>install              Install or update dependencies
</span></span><span style="display:flex;"><span>serve                Run the local development server
</span></span><span style="display:flex;"><span>update               Do apt upgrade and autoremove
</span></span></code></pre></div><p>Now we have our very own personalized and project-specific CLI tool!</p>
<p>The possibilities for improving your DevOps flow with a self-documenting Makefile are almost endless. You can use one to simplify any workflow and produce some very happy developers.</p>
<p>Please enjoy the (live!) Makefile I use to manage and develop this Hugo site. I hope it inspires you!</p>
<details>
<summary>My Hugo site Makefile</summary>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-Makefile" data-lang="Makefile"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>SHELL <span style="color:#f92672">:=</span> /bin/bash
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">.POSIX</span><span style="color:#f92672">:</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">.PHONY</span><span style="color:#f92672">:</span> help env install upgrade-hugo serve build start initial
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">help</span><span style="color:#f92672">:</span> <span style="color:#75715e">## Show this help
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>	@egrep -h <span style="color:#e6db74">&#39;\s##\s&#39;</span> <span style="color:#66d9ef">$(</span>MAKEFILE_LIST<span style="color:#66d9ef">)</span> | sort | awk <span style="color:#e6db74">&#39;BEGIN {FS = &#34;:.*?## &#34;}; {printf &#34;\033[36m%-20s\033[0m %s\n&#34;, $$1, $$2}&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">env</span><span style="color:#f92672">:</span>
</span></span><span style="display:flex;"><span>	pip3 install pipenv
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">shell</span><span style="color:#f92672">:</span> <span style="color:#75715e">## Enter the virtual environment
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>	pipenv shell
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">install</span><span style="color:#f92672">:</span> <span style="color:#75715e">## Install or update dependencies
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>	pipenv install --dev
</span></span><span style="display:flex;"><span>	pre-commit install --install-hooks
</span></span><span style="display:flex;"><span>	npm install
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>HUGO_VERSION<span style="color:#f92672">:=</span><span style="color:#66d9ef">$(</span>shell curl -s https://api.github.com/repos/gohugoio/hugo/releases/latest | grep <span style="color:#e6db74">&#39;tag_name&#39;</span> | cut -d <span style="color:#e6db74">&#39;&#34;&#39;</span> -f <span style="color:#ae81ff">4</span> | cut -c 2-<span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">upgrade-hugo</span><span style="color:#f92672">:</span> <span style="color:#75715e">## Get the latest Hugo
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>	mkdir tmp/ <span style="color:#f92672">&amp;&amp;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>	cd tmp/ <span style="color:#f92672">&amp;&amp;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>	curl -sSL https://github.com/gohugoio/hugo/releases/download/v<span style="color:#66d9ef">$(</span>HUGO_VERSION<span style="color:#66d9ef">)</span>/hugo_extended_<span style="color:#66d9ef">$(</span>HUGO_VERSION<span style="color:#66d9ef">)</span>_Linux-64bit.tar.gz | tar -xvzf- <span style="color:#f92672">&amp;&amp;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>	sudo mv hugo /usr/local/bin/ <span style="color:#f92672">&amp;&amp;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>	cd .. <span style="color:#f92672">&amp;&amp;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>	rm -rf tmp/
</span></span><span style="display:flex;"><span>	hugo version
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">dev</span><span style="color:#f92672">:</span> <span style="color:#75715e">## Run the local development server
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>	git submodule update --init --recursive
</span></span><span style="display:flex;"><span>	hugo serve --enableGitInfo --disableFastRender --environment development
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>TODAY <span style="color:#f92672">:=</span> <span style="color:#66d9ef">$(</span>shell date +%Y%m%d<span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">post</span><span style="color:#f92672">:</span> <span style="color:#75715e">## Create a new content/posts/ draft with TITLE=
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>	hugo new content/posts/<span style="color:#66d9ef">$(</span>TODAY<span style="color:#66d9ef">)</span>-<span style="color:#66d9ef">$(</span>TITLE<span style="color:#66d9ef">)</span>/index.md
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">future</span><span style="color:#f92672">:</span> <span style="color:#75715e">## Run the local development server in the future
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>	hugo serve --enableGitInfo --buildFuture --disableFastRender --environment development
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">build</span><span style="color:#f92672">:</span> <span style="color:#75715e">## Build site
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>	hugo --minify --cleanDestinationDir
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">initial</span><span style="color:#f92672">:</span> env install upgrade-hugo serve <span style="color:#75715e">## Install tools and start development server
</span></span></span></code></pre></div></details>
]]></content></entry><entry><title type="html">Climbing Mt. Fuji</title><link href="https://victoria.dev/archive/climbing-mt.-fuji/"/><id>https://victoria.dev/archive/climbing-mt.-fuji/</id><author><name>Victoria Drake</name></author><published>2020-08-02T06:35:45-04:00</published><updated>2020-08-02T06:35:45-04:00</updated><content type="html"><![CDATA[<p>In 2017, I climbed Mt. Fuji, in Japan.</p>
<p>Mt. Fuji is, some folks would say, the cakewalk of mountain climbing. Physically, the hardest portions amount to scrambling over some big boulders; most of it is no more taxing than a hike or climbing a set of stairs. For spiritual reasons, some Japanese folks make the climb at ages upwards of 80 years. There are huts to stop at along the way where you can rent a sleeping bag inside, and buy food and water. Naturally, having done this research and deciding it sounded like a fun outing, I arrived at basecamp in sneakers.</p>
<p>Most of the way up was amazing and thoroughly enjoyable. I saw sights I&rsquo;d never seen before, like the glow of a city under the sun through a break in the clouds, from above. Walking a path through a cloud was like taking a road into nothingness, with blank grey on all sides that weren&rsquo;t a mountain. Every time we hit a station marker, I felt pride and accomplishment.</p>
<p>Until it was time to summit.</p>
<p>Most of the people who climb Mt. Fuji wish to reach the summit at sunrise. Some for spiritual reasons, others for Instagram, and for those like myself, it just seemed like the thing to do. Regardless, it was because of these other 5,000 average daily climbers that I found myself in an actual queue that snaked the entire path from the last station hut to the summit &ndash; in the pitch black pre-dawn cold. It took hours, for most of which, we stood stock-still, going nowhere. I took to doing calisthenics to stave off frostbite from the cold that threatened my sneaker-shod toes.</p>
<p>We did, eventually, reach the summit, and before sunrise. It remains one of the most beautiful sunrises I&rsquo;ve seen &ndash; a pink-gold light that lit up the peak like breathing life into a painting, and that brought, mercifully, a degree of warmth. I was extremely happy, and felt pride and accomplishment.</p>
<p>Until it was time to descend.</p>
<p>There is a Japanese proverb: “A wise man will climb Mt Fuji once; a fool will climb Mt Fuji twice.” It is my own suspicion that this saying is based entirely on the difficulty of climbing down. The descent is essentially a loosely-packed, dirt and gravel road &ndash; on a decline. It is not, I imagine, significantly taxing with proper hiking boots, maybe snow tread, and a couple good spiked hiking poles thrown in. Wearing a pair of flat-soled street shoes, however, I fell. I fell often, and hard, about every three steps, for hours. I tried to take larger steps; it didn&rsquo;t help. I tried to take smaller steps; that didn&rsquo;t help, either. I tried cunningly to find a way to surf-slide my way down the mountainside and nearly ended up with a mouthful of dirt. As if literally rubbing salt into my wounds, without the gaiters I hadn&rsquo;t brought, sand found its way into my shoes. It was without a doubt the most stupefyingly discouraging experience of my life.</p>
<p>On several occasions, more seasoned (smarter? well-prepared?) hikers passed me, a good many of them at least twice my age. I&rsquo;m hard-pressed to remember another time in my life where I have been so thoroughly shown up by someone who might have been my grandmother, plunking hiking poles into the earth and sauntering past at a steady pace while I picked myself up, elbows scratched and covered in dirt, for the umpteenth time.</p>
<p>Eventually, we reached the bottom. At a tiny basecamp gift shop, I ate a delicious bowl of ramen and the tastiest sponge cake in the shape of a mountain that I&rsquo;ll likely ever have.</p>
<p>The experience drove home two lessons that have gone on to serve me well: one, that all the good research in the world will not guarantee your experience; and two, that even when faced with a discouraging situation that you can&rsquo;t seem to think yourself out of and thus the only way is &ldquo;through,&rdquo; there may still be something to learn from it, and there may be really good cake at the bottom.</p>
]]></content></entry><entry><title type="html">The Descent Is Harder Than the Climb</title><link href="https://victoria.dev/posts/the-descent-is-harder-than-the-climb/"/><id>https://victoria.dev/posts/the-descent-is-harder-than-the-climb/</id><author><name>Victoria Drake</name></author><published>2020-08-02T06:35:45-04:00</published><updated>2020-08-02T06:35:45-04:00</updated><content type="html"><![CDATA[<p>In 2017, I climbed Mt. Fuji in sneakers. This was not a deliberate choice to increase the challenge—it was the result of excellent research and poor judgment about what that research actually meant.</p>
<p>Everything I&rsquo;d read suggested that Mt. Fuji was the &ldquo;cakewalk of mountain climbing.&rdquo; Physically, the hardest portions amounted to scrambling over some big boulders. Most of the climb was no more taxing than hiking or climbing stairs. Japanese folks in their eighties made the journey for spiritual reasons. There were huts along the way for rest, food, and water. Based on this research, I concluded that sneakers would be perfectly adequate.</p>
<p>The ascent was everything I&rsquo;d been promised. I experienced sights I&rsquo;d never imagined—cities glowing through breaks in clouds from above, walking through paths of grey nothingness where the trail disappeared into cloud cover. Each station marker brought genuine pride and accomplishment. Even the pre-dawn summit queue with 5,000 other climbers, standing in freezing darkness for hours, felt manageable. We reached the summit before sunrise, and it remains one of the most beautiful moments I&rsquo;ve experienced.</p>
<p>Then came the descent. That&rsquo;s where I learned that all the research in the world about reaching goals doesn&rsquo;t prepare you for what comes after you achieve them.</p>
<h2 id="when-success-becomes-the-set-up-for-failure">When Success Becomes the Set Up for Failure</h2>
<p>The descent from Mt. Fuji is essentially a loosely-packed dirt and gravel road on a steep decline. With proper hiking boots and trekking poles, it&rsquo;s probably manageable. In flat-soled street shoes, I fell constantly, and fell hard—every three steps, for hours. I tried to take larger steps; it didn&rsquo;t help. I tried to take smaller steps; that didn&rsquo;t help, either. I tried cunningly to find a way to surf-slide my way down the mountainside and nearly ended up with a mouthful of dirt. As if literally rubbing salt into my wounds, without the gaiters I hadn&rsquo;t brought, sand found its way into my shoes. It was without a doubt the most stupefyingly discouraging experience of my life.</p>
<p>As I picked myself up repeatedly, covered in dirt with scratched elbows, seasoned hikers passed me with ease. Many of them could have been my grandparents, using proper equipment and technique to descend at a steady pace while I struggled and stopped to pour tiny rocks out of my sneakers. The contrast was humbling and instructive.</p>
<p>This experience taught me something crucial about leadership that I&rsquo;ve applied countless times since: the skills and preparation that get you to success are often different from the skills required to maintain or scale that success. The descent is frequently harder than the climb, and most people don&rsquo;t prepare for it adequately.</p>
<h2 id="the-post-achievement-challenge">The Post-Achievement Challenge</h2>
<p>In business and team leadership, I&rsquo;ve watched this pattern repeat consistently. The energy, skills, and resources required to achieve a goal are usually well-understood and planned for. But the challenges that come after success—maintaining market position, scaling team culture, or managing the operational complexity of growth—often catch leaders unprepared.</p>
<p>I&rsquo;ve seen teams that executed brilliant product launches struggle with customer support and maintenance. Startups that successfully raised funding stumble when it comes to executing on their promises to investors. Engineering teams that built innovative solutions fail to create sustainable systems for maintaining and scaling those solutions.
The problem isn&rsquo;t lack of capability—it&rsquo;s that the descent requires different preparation and different skills than the ascent. What gets you to the summit (innovation, speed, breakthrough thinking) often isn&rsquo;t what gets you safely back to basecamp (consistency, processes, systematic execution).</p>
<h1 id="learning-from-those-whove-made-the-journey">Learning from Those Who&rsquo;ve Made the Journey</h1>
<p>Watching those experienced hikers pass me on Mt. Fuji was initially frustrating, but it became one of the most valuable parts of the experience. They had proper equipment, understood the terrain, and moved with confidence that came from experience. Most importantly, they had prepared specifically for the descent, not just the climb.</p>
<p>In leadership roles, I&rsquo;ve learned to actively seek out people who&rsquo;ve successfully navigated the &ldquo;descent&rdquo; phase of challenges I&rsquo;m facing. Entrepreneurs who&rsquo;ve managed hypergrowth. Product managers who&rsquo;ve maintained market leadership over multiple years. Engineering leaders who&rsquo;ve scaled teams from ten to fifty people, or CEOs who’ve scaled companies from fifty to five hundred.</p>
<p>These conversations can reveal patterns you may not have discovered on your own. Successful scaling requires different organizational structures than startup growth. Maintaining team culture during rapid hiring requires intentional systems that don&rsquo;t emerge naturally. Sustaining innovation while managing operational complexity demands new kinds of leadership skills.</p>
<p>People who&rsquo;ve successfully managed the descent often have hard-won wisdom about preparation and technique that isn&rsquo;t captured in most &ldquo;how to reach the summit&rdquo; advice.</p>
<h2 id="building-skills-before-you-need-them">Building Skills Before You Need Them</h2>
<p>The most effective leaders I know prepare for post-success challenges while they&rsquo;re still climbing toward their initial goals. They think systematically about what will be required to maintain and scale whatever they&rsquo;re building, not just achieve it.</p>
<p>This means building operational capabilities alongside product capabilities. Developing team management skills in individual contributors. Creating sustainable processes while you&rsquo;re still in startup mode. Planning for the maintenance and evolution of systems as part of their initial implementation.</p>
<p>It also means recognizing that the mindset and skills that drive breakthrough achievements—risk-taking, speed, creative problem-solving—need to be balanced with different capabilities like consistency, systematic thinking, and process optimization.
I&rsquo;ve learned to explicitly ask: &ldquo;What will success look like, and what challenges will that create?&rdquo; This question reveals preparation gaps that aren&rsquo;t obvious when you&rsquo;re focused entirely on reaching your goals.</p>
<h2 id="when-you-find-yourself-unprepared">When You Find Yourself Unprepared</h2>
<p>Despite best intentions, you&rsquo;ll sometimes find yourself in descent mode without proper preparation—leading a team through unexpected growth, managing a product that succeeded beyond projections, or scaling systems that weren&rsquo;t designed for current loads. The Mt. Fuji experience taught me how to navigate these situations effectively.</p>
<p>First, acknowledge the reality of your situation without wasting energy on regret about preparation gaps. You can&rsquo;t change what you didn&rsquo;t know or plan for previously, but you can adapt your approach based on current conditions. Take the time to solidify new goals in writing, then evaluate whether your efforts are serving them effectively.</p>
<p>Second, focus on learning from people who are managing similar challenges successfully. This isn&rsquo;t the time for pride or trying to figure everything out independently. The hikers who passed me weren&rsquo;t showing off—they had practical knowledge that could help. Conversations you have with others who came before you can save you from a lot of stumbles.</p>
<p>Third, lift your gaze. While the ascent phase requires day-to-day tactical thinking, the descent phase requires a strategic longer-term outlook. Implementing systems and culture that support continued success will require patience, persistence, and often a completely different pace than what got you to the summit. Expecting it to be as expedient as the climb leads to frustration and poor decision-making.</p>
<h2 id="finding-meaning-in-the-difficult-parts">Finding Meaning in the Difficult Parts</h2>
<p>Eventually, I reached the bottom of Mt. Fuji, exhausted and humbled but intact. At a tiny basecamp shop, I ate the most delicious bowl of ramen and the tastiest mountain-shaped sponge cake I&rsquo;ll likely ever have.</p>
<p>Even when you&rsquo;re unprepared and struggling, there&rsquo;s value in the journey itself. The descent taught me lessons about preparation, humility, and persistence that I&rsquo;ve applied to all sorts of challenges for years since.</p>
<h2 id="preparing-for-your-next-descent">Preparing for Your Next Descent</h2>
<p>There is a Japanese proverb: “A wise man will climb Mt Fuji once; a fool will climb Mt Fuji twice.” I suspect this wisdom is based entirely on the difficulty of the descent. But in leadership, you don&rsquo;t get to choose how many times you&rsquo;ll face descent challenges—they&rsquo;re inevitable parts of any significant journey.</p>
<p>The key is recognizing that achieving your goals is often just the beginning of a different kind of challenge. Success creates new problems that require different skills, different preparation, and different mindsets than what got you there initially.</p>
<p>Whether you&rsquo;re building teams, scaling products, or managing organizational growth, prepare for the descent while you&rsquo;re planning the climb. Study what happens after success. Learn from people who&rsquo;ve navigated similar transitions. Build operational capabilities alongside innovative ones.</p>
<p>Most importantly, remember that the descent is still part of the journey, not a failure of the ascent. The challenges that come with success are signs that you&rsquo;ve accomplished something meaningful. Navigate them with patience, preparation, and the understanding that getting back to basecamp safely can be an even more important achievement than reaching the summit.</p>
]]></content></entry><entry><title type="html">Go automate your GitHub profile README</title><link href="https://victoria.dev/archive/go-automate-your-github-profile-readme/"/><id>https://victoria.dev/archive/go-automate-your-github-profile-readme/</id><author><name>Victoria Drake</name></author><published>2020-07-25T10:51:15-04:00</published><updated>2020-07-25T10:51:15-04:00</updated><content type="html"><![CDATA[<p>GitHub&rsquo;s new profile page README feature is having the wonderful effect of bringing some personality to the Myspace pages of the developer Internet. Though Markdown lends itself best to standard static text content, that&rsquo;s not stopping creative folks from working to create a next-level README. You can include GIFs and images to add some motion and pizazz (they&rsquo;re covered in <a href="https://github.github.com/gfm/">GitHub Flavor Markdown</a>), but I&rsquo;m thinking of something a little more dynamic.</p>
<p>At front-and-center on your GitHub profile, your README is a great opportunity to let folks know what you&rsquo;re about, what you find important, and to showcase some highlights of your work. You might like to show off your latest repositories, tweet, or blog post. Keeping it up to date doesn&rsquo;t have to be a pain either, thanks to continuous delivery tools like GitHub Actions.</p>
<p>My current README refreshes itself daily with a link to my latest blog post. Here&rsquo;s how I&rsquo;m creating a self-updating <code>README.md</code> with Go and GitHub actions.</p>
<h2 id="reading-and-writing-files-with-go">Reading and writing files with Go</h2>
<p>I&rsquo;ve been writing a lot of Python lately, but for some things I really like using Go. You could say it&rsquo;s my go-to language for just-for-<code>func</code> projects. Sorry. Couldn&rsquo;t stop myself.</p>
<p>To create my README.md, I&rsquo;m going to get some static content from an existing file, mash it together with some new dynamic content that we&rsquo;ll generate with Go, then bake the whole thing at 400 degrees until something awesome comes out.</p>
<p>Here&rsquo;s how we read in a file called <code>static.md</code> and put it in <code>string</code> form:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// Unwrap Markdown content
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">content</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">ioutil</span>.<span style="color:#a6e22e">ReadFile</span>(<span style="color:#e6db74">&#34;static.md&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Fatalf</span>(<span style="color:#e6db74">&#34;cannot read file: %v&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Make it a string
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">stringyContent</span> <span style="color:#f92672">:=</span> string(<span style="color:#a6e22e">content</span>)
</span></span></code></pre></div><p>The possibilities for your dynamic content are only limited by your imagination! Here, I&rsquo;ll use the <a href="https://github.com/mmcdole/gofeed"><code>github.com/mmcdole/gofeed</code> package</a> to read the RSS feed from my blog and get the newest post.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">fp</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">gofeed</span>.<span style="color:#a6e22e">NewParser</span>()
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">feed</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">fp</span>.<span style="color:#a6e22e">ParseURL</span>(<span style="color:#e6db74">&#34;https://victoria.dev/index.xml&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Fatalf</span>(<span style="color:#e6db74">&#34;error getting feed: %v&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Get the freshest item
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">rssItem</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">feed</span>.<span style="color:#a6e22e">Items</span>[<span style="color:#ae81ff">0</span>]
</span></span></code></pre></div><p>To join these bits together and produce stringy goodness, we use <a href="https://golang.org/pkg/fmt/#Sprintf"><code>fmt.Sprintf()</code></a> to create a formatted string.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// Whisk together static and dynamic content until stiff peaks form
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">blog</span> <span style="color:#f92672">:=</span> <span style="color:#e6db74">&#34;Read my latest blog post: **[&#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">rssItem</span>.<span style="color:#a6e22e">Title</span> <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;](&#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">rssItem</span>.<span style="color:#a6e22e">Link</span> <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;)**&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">data</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Sprintf</span>(<span style="color:#e6db74">&#34;%s\n%s\n&#34;</span>, <span style="color:#a6e22e">stringyContent</span>, <span style="color:#a6e22e">blog</span>)
</span></span></code></pre></div><p>Then to create a new file from this mix, we use <a href="https://golang.org/pkg/os/#Create"><code>os.Create()</code></a>. There are <a href="https://www.joeshaw.org/dont-defer-close-on-writable-files/">more things to know about deferring <code>file.Close()</code></a>, but we don&rsquo;t need to get into those details here. We&rsquo;ll add <code>file.Sync()</code> to ensure our README gets written.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// Prepare file with a light coating of os
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">file</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Create</span>(<span style="color:#e6db74">&#34;README.md&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">file</span>.<span style="color:#a6e22e">Close</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Bake at n bytes per second until golden brown
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">io</span>.<span style="color:#a6e22e">WriteString</span>(<span style="color:#a6e22e">file</span>, <span style="color:#a6e22e">data</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">return</span> <span style="color:#a6e22e">file</span>.<span style="color:#a6e22e">Sync</span>()
</span></span></code></pre></div><p>View the full code <a href="https://github.com/victoriadrake/victoriadrake/blob/535da81efd291e40374307609a9fa66e08f4985c/update/main.go">here in my README repository</a>.</p>
<p>Mmmm, doesn&rsquo;t that smell good? 🍪 Let&rsquo;s make this happen on the daily with a GitHub Action.</p>
<h2 id="running-your-go-program-on-a-schedule-with-actions">Running your Go program on a schedule with Actions</h2>
<p>You can create a GitHub Action workflow that <a href="https://docs.github.com/en/actions/reference/events-that-trigger-workflows">triggers</a> both on a push to your <code>master</code> branch as well as on a daily schedule. Here&rsquo;s a slice of the <code>.github/workflows/update.yaml</code> that defines this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">on</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">push</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">branches</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#ae81ff">master</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">schedule</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">cron</span>: <span style="color:#e6db74">&#39;0 11 * * *&#39;</span>
</span></span></code></pre></div><p>To run the Go program that rebuilds our README, we first need a copy of our files. We use <code>actions/checkout</code> for that:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">steps</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">🍽️ Get working copy</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/checkout@master</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">with</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">fetch-depth</span>: <span style="color:#ae81ff">1</span>
</span></span></code></pre></div><p>This step runs our Go program:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#f92672">name</span>: <span style="color:#ae81ff">🍳 Shake &amp; bake README</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">run</span>: |<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    cd ${GITHUB_WORKSPACE}/update/
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    go run main.go</span>    
</span></span></code></pre></div><p>Finally, we push the updated files back to our repository. Learn more about the variables shown at <a href="https://docs.github.com/en/actions/configuring-and-managing-workflows/using-variables-and-secrets-in-a-workflow">Using variables and secrets in a workflow</a>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#f92672">name</span>: <span style="color:#ae81ff">🚀 Deploy</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">run</span>: |<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    git config user.name &#34;${GITHUB_ACTOR}&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    git config user.email &#34;${GITHUB_ACTOR}@users.noreply.github.com&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    git add .
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    git commit -am &#34;Update dynamic content&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    git push --all -f https://${{ secrets.GITHUB_TOKEN }}@github.com/${GITHUB_REPOSITORY}.git</span>    
</span></span></code></pre></div><p>View the full code for this Action workflow <a href="https://github.com/victoriadrake/victoriadrake/blob/535da81efd291e40374307609a9fa66e08f4985c/.github/workflows/update.yaml">here in my README repository</a>.</p>
<h2 id="go-forth-and-auto-update-your-readme">Go forth and auto-update your README</h2>
<p>Congratulations and welcome to the cool kids&rsquo; club! You now know how to build an auto-updating GitHub profile README. You may now go forth and add all sorts of neat dynamic elements to your page &ndash; just go easy on the GIFs, okay?</p>
]]></content></entry><entry><title type="html">Writing efficient Django</title><link href="https://victoria.dev/archive/writing-efficient-django/"/><id>https://victoria.dev/archive/writing-efficient-django/</id><author><name>Victoria Drake</name></author><published>2020-07-09T04:02:47-05:00</published><updated>2020-07-09T04:02:47-05:00</updated><content type="html"><![CDATA[<p>I like Django. It&rsquo;s a well-considered and intuitive framework with a name I can pronounce out loud. You can use it to quickly spin up a weekend-sized project, and you can still use it to run <a href="https://applybyapi.com">full-blown production applications</a> at scale. I&rsquo;ve done both these things, and over the years I&rsquo;ve discovered how to use some of Django&rsquo;s features for maximum efficiency. These are:</p>
<ul>
<li><a href="#class-based-versus-function-based-views">Class-based versus function-based views</a></li>
<li><a href="#django-models">Django models</a></li>
<li><a href="#retrieving-objects-with-queries">Retrieving objects with queries</a></li>
</ul>
<p>Understanding these main features are the building blocks for maximizing development efficiency with Django. They&rsquo;ll build the foundation for you to <a href="/posts/increase-developer-confidence-with-a-great-django-test-suite/">test efficiently</a> and <a href="/blog/django-project-best-practices-to-keep-your-developers-happy/">create an awesome development experience for your engineers</a>. Let&rsquo;s look at how these tools let you create a performant Django application that&rsquo;s pleasant to build and maintain.</p>
<h2 id="class-based-versus-function-based-views">Class-based versus function-based views</h2>
<p>Remember that Django is all Python under the hood. When it comes to views, you&rsquo;ve got two choices: <a href="https://docs.djangoproject.com/en/3.2/topics/http/views/">view functions</a> (sometimes called &ldquo;function-based views&rdquo;), or <a href="https://docs.djangoproject.com/en/3.2/topics/class-based-views/">class-based views</a>.</p>
<p>Years ago when I first built <a href="https://applybyapi.com">ApplyByAPI</a>, it was initially composed entirely of function-based views. These offer granular control, and are good for implementing complex logic; just as in a Python function, you have complete control (for better or worse) over what the view does. With great control comes great responsibility, and function-based views can be a little tedious to use. You&rsquo;re responsible for writing all the necessary methods for the view to work - this is what allows you to completely tailor your application.</p>
<p>In the case of ApplyByAPI, there were only a sparse few places where that level of tailored functionality was really necessary. Everywhere else, function-based views began making my life harder. Writing what is essentially a custom view for run-of-the-mill operations like displaying data on a list page became tedious, repetitive, and error-prone.</p>
<p>With function-based views, you&rsquo;ll need figure out which Django methods to implement in order to handle requests and pass data to views. Unit testing can take some work to write. In short, the granular control that function-based views offer also requires some granular tedium to properly implement.</p>
<p>I ended up holding back ApplyByAPI while I refactored the majority of the views into class-based views. This was not a small amount of work and refactoring, but when it was done, I had a bunch of tiny views that made a huge difference. I mean, just look at this one:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">ApplicationsList</span>(ListView):
</span></span><span style="display:flex;"><span>    model <span style="color:#f92672">=</span> Application
</span></span><span style="display:flex;"><span>    template_name <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;applications.html&#34;</span>
</span></span></code></pre></div><p>It&rsquo;s three lines. My developer ergonomics, and my life, got a lot easier.</p>
<p>You may think of class-based views as templates that cover most of the functionality any app needs. There are views for displaying lists of things, for viewing a thing in detail, and <a href="https://docs.djangoproject.com/en/3.2/ref/class-based-views/generic-editing/">editing views</a> for performing CRUD (Create, Read, Update, Delete) operations. Because implementing one of these generic views takes only a few lines of code, my application logic became dramatically succinct. This gave me less repeated code, fewer places for something to go wrong, and a more manageable application in general.</p>
<p>Class-based views are fast to implement and use. The built-in class-based generic views may require less work to test, since you don&rsquo;t need to write tests for the base view Django provides. (Django does its own tests for that; no need for your app to double-check.) To tweak a generic view to your needs, you can <a href="https://docs.djangoproject.com/en/3.2/topics/class-based-views/#subclassing-generic-views">subclass a generic view</a> and override attributes or methods. In my case, since I only needed to write tests for any customizations I added, my test files became dramatically shorter, as did the time and resources it took to run them.</p>
<p>When you&rsquo;re weighing the choice between function-based or class-based views, consider the amount of customization the view needs, and the future work that will be necessary to test and maintain it. If the logic is common, you may be able to hit the ground running with a generic class-based view. If you need sufficient granularity that re-writing a base view&rsquo;s methods would make it overly complicated, consider a function-based view instead.</p>
<h2 id="django-models">Django models</h2>
<p><a href="https://docs.djangoproject.com/en/3.2/topics/db/models/">Models</a> organize your Django application&rsquo;s central concepts to help make them flexible, robust, and easy to work with. If used wisely, models are a powerful way to collate your data into a definitive source of truth.</p>
<p>Like views, Django provides some built-in model types for the convenience of implementing basic authentication, including the <a href="https://docs.djangoproject.com/en/3.2/ref/contrib/auth/">User</a> and <a href="https://docs.djangoproject.com/en/3.2/ref/contrib/auth/">Permission</a> models. For everything else, you can create a model that reflects your concept by <a href="https://docs.djangoproject.com/en/3.2/topics/db/models/#model-inheritance">inheriting from a parent Model class</a>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">StaffMember</span>(models<span style="color:#f92672">.</span>Model):
</span></span><span style="display:flex;"><span>    user <span style="color:#f92672">=</span> models<span style="color:#f92672">.</span>OneToOneField(User, on_delete<span style="color:#f92672">=</span>models<span style="color:#f92672">.</span>CASCADE)
</span></span><span style="display:flex;"><span>    company <span style="color:#f92672">=</span> models<span style="color:#f92672">.</span>OneToOneField(Company, on_delete<span style="color:#f92672">=</span>models<span style="color:#f92672">.</span>CASCADE)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> __str__(self):
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> self<span style="color:#f92672">.</span>company<span style="color:#f92672">.</span>name <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34; - &#34;</span> <span style="color:#f92672">+</span> self<span style="color:#f92672">.</span>user<span style="color:#f92672">.</span>email
</span></span></code></pre></div><p>When you create a custom model in Django, you subclass <a href="https://github.com/django/django/blob/master/django/db/models/base.py">Django&rsquo;s Model class</a> and take advantage of all its power. Each model you create generally maps to a database table. Each attribute is a database field. This gives you the ability to create objects to work with that humans can better understand.</p>
<p>You can make a model useful to you by defining its fields. Many <a href="https://docs.djangoproject.com/en/3.2/ref/models/fields/#model-field-types">built-in field types</a> are conveniently provided. These help Django figure out the data type, the HTML <a href="https://docs.djangoproject.com/en/3.2/ref/forms/widgets/">widget</a> to use when rendering a form, and even <a href="https://docs.djangoproject.com/en/3.2/ref/forms/validation/">form validation</a> requirements. If you need to, you can <a href="https://docs.djangoproject.com/en/3.2/howto/custom-model-fields/">write custom model fields</a>.</p>
<p>Database <a href="https://docs.djangoproject.com/en/3.2/topics/db/models/#relationships">relationships</a> can be defined using a <a href="https://docs.djangoproject.com/en/3.2/ref/models/fields/#django.db.models.ForeignKey">ForeignKey</a> field (many-to-one), or a <a href="https://docs.djangoproject.com/en/3.2/ref/models/fields/#django.db.models.ManyToManyField">ManyToManyField</a> (give you three guesses). If those don&rsquo;t suffice, there&rsquo;s also a <a href="https://docs.djangoproject.com/en/3.2/ref/models/fields/#django.db.models.OneToOneField">OneToOneField</a>. Together, these allow you to define relations between your models with levels of complexity limited only by your imagination. (Depending on the imagination you have, this may or may not be an advantage.)</p>
<h2 id="retrieving-objects-with-queries">Retrieving objects with queries</h2>
<p>Use your model&rsquo;s Manager (<code>objects</code> by default) to <a href="https://docs.djangoproject.com/en/3.2/topics/db/queries/#retrieving-objects">construct a QuerySet</a>. This is a representation of objects in your database that you can refine, using methods, to retrieve specific subsets. All available methods are in the <a href="https://docs.djangoproject.com/en/3.2/ref/models/querysets/#django.db.models.query.QuerySet">QuerySet API</a> and can be chained together for even more fun.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py" data-lang="py"><span style="display:flex;"><span>Post<span style="color:#f92672">.</span>objects<span style="color:#f92672">.</span>filter(
</span></span><span style="display:flex;"><span>    type<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;new&#34;</span>
</span></span><span style="display:flex;"><span>)<span style="color:#f92672">.</span>exclude(
</span></span><span style="display:flex;"><span>    title__startswith<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Blockchain&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><p>Some methods return new QuerySets, such as <a href="https://docs.djangoproject.com/en/3.2/ref/models/querysets/#filter"><code>filter()</code></a>, or <a href="https://docs.djangoproject.com/en/3.2/ref/models/querysets/#exclude"><code>exclude()</code></a>. Chaining these can give you powerful queries without affecting performance, as QuerySets aren&rsquo;t fetched from the database <a href="https://docs.djangoproject.com/en/3.2/ref/models/querysets/#when-querysets-are-evaluated">until they are evaluated</a>. Methods that evaluate a QuerySet include <code>get()</code>, <a href="https://docs.djangoproject.com/en/3.2/ref/models/querysets/#count"><code>count()</code></a>, <code>len()</code>, <code>list()</code>, or <code>bool()</code>.</p>
<p>Iterating over a QuerySet also evaluates it, so avoid doing so where possible to improve query performance. For instance, if you just want to know if an object is present, you can use <code>exists()</code> to avoid iterating over database objects.</p>
<p>Use <a href="https://docs.djangoproject.com/en/3.2/ref/models/querysets/#django.db.models.query.QuerySet.get"><code>get()</code></a> in cases where you want to retrieve a specific object. This method raises <a href="https://docs.djangoproject.com/en/3.2/ref/exceptions/#django.core.exceptions.MultipleObjectsReturned"><code>MultipleObjectsReturned</code></a> if something unexpected happens, as well as the <code>DoesNotExist</code> exception, if, take a guess.</p>
<p>If you&rsquo;d like to get an object that may not exist in the context of a user&rsquo;s request, use the convenient <a href="https://docs.djangoproject.com/en/3.2/topics/http/shortcuts/#get-object-or-404"><code>get_object_or_404()</code></a> or <a href="https://docs.djangoproject.com/en/3.2/topics/http/shortcuts/#get-list-or-404"><code>get_list_or_404()</code></a> which raises <a href="https://docs.djangoproject.com/en/3.2/topics/http/views/#django.http.Http404"><code>Http404</code></a> instead of <a href="https://docs.djangoproject.com/en/3.2/ref/models/instances/#django.db.models.Model.DoesNotExist"><code>DoesNotExist</code></a>. These helpful <a href="https://docs.djangoproject.com/en/3.2/topics/http/shortcuts/">shortcuts</a> are suited to just this purpose. To create an object that doesn&rsquo;t exist, there&rsquo;s also the convenient <a href="https://docs.djangoproject.com/en/3.2/ref/models/querysets/#get-or-create"><code>get_or_create()</code></a>.</p>
<!-- omit in toc -->
<h2 id="efficient-essentials">Efficient essentials</h2>
<p>You&rsquo;ve now got a handle on these three essential tools for building your efficient Django application &ndash; congratulations! You can make Django work even better for you by learning about <a href="/posts/manipulating-data-with-django-migrations/">manipulating data with migrations</a>, <a href="/posts/increase-developer-confidence-with-a-great-django-test-suite/">testing effectively</a>, and <a href="/blog/django-project-best-practices-to-keep-your-developers-happy/">setting up your team&rsquo;s Django development for maximum happiness</a>.</p>
<p>If you&rsquo;re going to build on GitHub, you may like to set up my <a href="https://github.com/victoriadrake/django-security-check">django-security-check GitHub Action</a>. In the meantime, you&rsquo;re well on your way to building a beautiful software project.</p>
]]></content></entry><entry><title type="html">Look mom, I&amp;#39;m a GitHub Action Hero</title><link href="https://victoria.dev/archive/look-mom-im-a-github-action-hero/"/><id>https://victoria.dev/archive/look-mom-im-a-github-action-hero/</id><author><name>Victoria Drake</name></author><published>2020-06-27T09:06:33-04:00</published><updated>2020-06-27T09:06:33-04:00</updated><content type="html"><![CDATA[<p>GitHub recently interviewed me for their blog editorial entitled <a href="https://github.blog/2020-06-26-github-action-hero-victoria-drake/">GitHub Action Hero: Victoria Drake</a>. Here&rsquo;s a behind-the-scenes peek at the original interview questions and my answers.</p>
<h2 id="what-is-the-name-of-your-action-please-include-a-link-too">What is the name of your Action? Please include a link too.</h2>
<p>Among the several Actions I&rsquo;ve built, I have two current favorites. One is <a href="https://github.com/victoriadrake/hugo-remote">hugo-remote</a>, which lets you continuously deploy a Hugo static site from a private source repository to a public GitHub Pages repository. This keeps the contents of the source repository private, such as your unreleased drafts, while still allowing you to have a public open source site using GitHub Pages.</p>
<p>The second is <a href="https://github.com/victoriadrake/django-security-check">django-security-check</a>. It&rsquo;s an effortless way to continuously check that your production Django application is free from a variety of security misconfigurations. You can think of it as your little CI/CD helper for busy projects &ndash; a security linter!</p>
<h2 id="tell-us-a-little-bit-more-about-yourselfhow-did-you-get-started-in-software-tools">Tell us a little bit more about yourself—how did you get started in software tools?</h2>
<p>When I was a kid, I spent several summer vacations coding a huge medieval fantasy world MUD (Multi-User Dungeon, like a multiplayer role-playing game) written in LPC, with friends. It was entirely text-based, and built and played via Telnet. I fell in love with the terminal and learned a lot about object-oriented programming and prototype-based programming early on.</p>
<p>I became a freelance developer and had the privilege of working on a wide variety of client projects. Realizing the difficulty that companies have with hiring experienced developers, I built <a href="https://ApplyByAPI.com">ApplyByAPI.com</a> to help. As you might imagine, it allows candidates to apply for jobs via API, instead of emailing a resume. It&rsquo;s based on the Django framework, so in the process, I learned even more about building reusable units of software.</p>
<p>When I became a co-author and a core maintainer for the <a href="https://github.com/OWASP/wstg">Open Web Application Security Project (OWASP) Web Security Testing Guide (WSTG)</a>, I gained an even broader appreciation for how a prototype-based, repeatable approach can help build secure web applications. Organizations worldwide consider the WSTG the foremost open source resource for testing the security of web applications. We&rsquo;ve applied this thinking via the use of GitHub Actions in our repository &ndash; I&rsquo;ll tell you more about that later.</p>
<p>Whether I&rsquo;m creating an open source tool or leading a development team, my childhood experience still informs how I think about programming today. I strive to create repeatable units of software like GitHub Actions &ndash; only now, I make them for large enterprises in the real world!</p>
<h2 id="what-is-the-story-behind-your-built-github-action-why-did-you-build-this">What is the story behind your built GitHub Action? (Why did you build this?)</h2>
<p>Developers take on a lot of responsibility when it comes to building secure applications these days. I&rsquo;m a full-time senior software developer at a cybersecurity company. I&rsquo;ve found that I&rsquo;m maximally productive when I create systems and processes that help myself and my team make desired outcomes inevitable. So I spend my free time building tools that make it easy for other developers to build secure software as well. My Actions help to automate contained, repeatable units of work that can make a big difference in a developer&rsquo;s day.</p>
<h2 id="do-you-have-future-plans-for-this-or-other-actions">Do you have future plans for this or other Actions?</h2>
<p>Yes! I&rsquo;m always finding ways for tools like GitHub Actions to boost the velocity of technical teams, whether at work or in my open source projects. Remember the Open Web Application Security Project? In the work I&rsquo;ve lead with OWASP, I&rsquo;ve championed the effort to increase automation using GitHub Actions to maintain quality, securely deploy new versions to the web, and even build PDFs of the WSTG. We&rsquo;re constantly looking into new ways that GitHub Actions can make our lives easier and our readers&rsquo; projects more secure.</p>
<h2 id="what-has-been-your-favorite-feature-of-github-actions">What has been your favorite feature of GitHub Actions?</h2>
<p>I like that I can build an Action using familiar and portable technologies, like Docker. Actions are easy for collaborators to work on too, since in the case of a Dockerized Action, you can use any language your team is comfortable with. This is especially useful in large organizations with polyglot teams and environments. There aren&rsquo;t any complicated dependencies for running these portable tasks, and you don&rsquo;t need to learn any special frameworks to get started.</p>
<p>One of my first blog posts about GitHub Actions even describes how I used an Action to run a Makefile! This is especially useful for large legacy applications that want to modernize their pipeline by using GitHub Actions.</p>
<h2 id="what-are-the-biggest-challenges-youve-faced-while-building-your-github-action">What are the biggest challenges you’ve faced while building your GitHub Action?</h2>
<p>The largest challenge of GitHub Actions isn&rsquo;t really in GitHub Actions, but in the transition of legacy software and company culture.</p>
<p>Migrating legacy software is always challenging, particularly with large legacy applications. Moving to modern CI/CD processes requires changes at the software level, team level, and even a shift in thinking when it comes to individual developers. It can help to have a tool like GitHub Actions, which is at once seamlessly modern and familiar, when transitioning legacy code to a modern pipeline.</p>
<h2 id="anything-else-you-would-like-to-share-about-your-experience-any-stories-or-lessons-learned-through-building-your-action">Anything else you would like to share about your experience? Any stories or lessons learned through building your Action?</h2>
<p>I&rsquo;m happiest when I&rsquo;m solving a challenge that makes developing secure software less challenging in the future, both for myself and for the technology organization I&rsquo;m leading. With tools like GitHub Actions, a lot of mental overhead can be offloaded to automatic processes &ndash; like getting a whole other brain, for free! This can massively help organizations that are ready to scale up their development output.</p>
<p>In the realm of cybersecurity, not only does creating portable and reusable software make developers&rsquo; lives easier, it helps to make whole workflows repeatable, which in turn makes software development processes more secure. With smart processes in place, technical teams are happier. As an inevitable result, they&rsquo;ll build better software for customers, too.</p>
]]></content></entry><entry><title type="html">Technical ergonomics for the efficient developer</title><link href="https://victoria.dev/archive/technical-ergonomics-for-the-efficient-developer/"/><id>https://victoria.dev/archive/technical-ergonomics-for-the-efficient-developer/</id><author><name>Victoria Drake</name></author><published>2020-06-22T06:33:28-04:00</published><updated>2020-06-22T06:33:28-04:00</updated><content type="html"><![CDATA[<p>This article isn&rsquo;t going to tell you about saving your neck with a Roost stand, or your wrists with a split keyboard - <a href="https://heronebag.com/blog/next-level-ergonomics-for-remote-work-developers/">I&rsquo;ve already done that</a>. This article is about saving your brain.</p>
<p>When I first began to program full time, I found myself constantly tired from the mental exertion. Programming is hard! Thankfully, you can take some solace in knowing it gets easier with practice, and with a great supporting cast. Some very nice folks who preceded us both came up with tools to make the difficult bits of communicating with computers much easier on our poor human meatbrains.</p>
<p>I invite you to explore these super helpful technical tools. They&rsquo;ll improve your development set up and alleviate much of the mental stress of programming. You soon won&rsquo;t believe you could have done without them.</p>
<h2 id="not-your-average-syntax-highlighting">Not your average syntax highlighting</h2>
<p>If you&rsquo;re still working with syntax highlighting that just picks out variable and class names for you, that&rsquo;s cute. Time to turn it up a notch.</p>
<p><img src="Screenshot_20200612_185858.png" alt="My current VSC theme and syntax highlighting"></p>
<p>In all seriousness, syntax highlighting can make it much easier to find what you&rsquo;re looking for on your screen: the current line, where your current code block starts and ends, or the absolute game-changing which-bracket-set-am-I-in highlight. I primarily use Visual Studio Code, but similar extensions can be found for the major text editors.</p>
<p>The theme pictured in Visual Studio Code above is <a href="https://github.com/victoriadrake/kabukicho-vscode">Kabukichō</a>. I made it.</p>
<h2 id="use-git-hooks">Use Git hooks</h2>
<p>I previously brought you <a href="/blog/an-automatic-interactive-pre-commit-checklist-in-the-style-of-infomercials/">an interactive pre-commit checklist in the style of infomercials</a> that&rsquo;s both fun and useful for reinforcing the quality of your commits. But that&rsquo;s not all!</p>
<p>Git hooks are scripts that run automatically at pre-determined points in your workflow. Use them well, and you can save a ton of brainpower. A <code>pre-commit</code> hook remembers to do things like lint and format code, and even runs local tests for you before you indelibly push something embarrassing. Hooks can be a little annoying to share (the <code>.git/hooks</code> directory isn&rsquo;t tracked and thus omitted when you clone or fork a repository) but there&rsquo;s a framework for that: the confusingly-named <a href="https://pre-commit.com/">pre-commit framework</a>, which allows you to create a sharable configuration file of Git hook plugins, not just for <code>pre-commit</code>.</p>
<p>I spend a majority of my time these days coding in Python, so here is my current <code>.pre-commit-config.yaml</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#ae81ff">fail_fast: true
repos:
  - repo: https://github.com/DavidAnson/markdownlint-cli2
    rev: v0.1.3
    hooks:
    - id: markdownlint-cli2
      name: markdownlint-cli2
      description: "Checks the style of Markdown/CommonMark files."
      entry: markdownlint-cli2
      language: node
      types: [markdown]
      minimum_pre_commit_version: 0.15.0</span>
</span></span></code></pre></div><p>There are tons of <a href="https://pre-commit.com/hooks.html">supported hooks</a> to explore.</p>
<h2 id="use-a-type-system">Use a type system</h2>
<p>If you write in languages like Python and JavaScript, get yourself an early birthday present and start using a static type system. Not only will this help improve the way you think about code, it can help make type errors clear before running a single line.</p>
<p>For Python, I like using <a href="https://github.com/python/mypy">mypy</a> for static type checking. You can set it up as a <code>pre-commit</code> hook (see above) and it&rsquo;s <a href="https://code.visualstudio.com/docs/python/linting#_mypy">supported in Visual Studio Code too</a>.</p>
<p><a href="https://www.typescriptlang.org/">TypeScript</a> is my preferred way to write JavaScript. You can run the compiler on the command line using Node.js (see <a href="https://github.com/Microsoft/TypeScript">instructions in the repo</a>), it works pretty well <a href="https://code.visualstudio.com/Docs/languages/typescript">with Visual Studio Code</a> out of the box, and of course there are multiple options for <a href="https://code.visualstudio.com/Docs/languages/typescript#_typescript-extensions">extension integrations</a>.</p>
<h2 id="quit-unnecessarily-beating-up-your-meatbrain">Quit unnecessarily beating up your meatbrain</h2>
<p>I mean, you wouldn&rsquo;t stand on your head all day to do your work. It would be rather inconvenient to read things upside down all the time (at least <a href="https://www.youtube.com/watch?v=jKUVpBJalNQ">until your brain adjusted</a>), and in any case you&rsquo;d likely get uncomfortably congested in short order. Working without taking advantage of the technical ergonomic tools I&rsquo;ve given you today is a little like unnecessary inversion - why would you, if you don&rsquo;t have to?</p>
]]></content></entry><entry><title type="html">How to choose and care for a secure open source project</title><link href="https://victoria.dev/archive/how-to-choose-and-care-for-a-secure-open-source-project/"/><id>https://victoria.dev/archive/how-to-choose-and-care-for-a-secure-open-source-project/</id><author><name>Victoria Drake</name></author><published>2020-05-25T05:53:09-04:00</published><updated>2020-05-25T05:53:09-04:00</updated><content type="html"><![CDATA[<p>There is a rather progressive sect of the software development world that believes that most people would be a lot happier and get a lot more work done if they just stopped building things that someone else has already built and is offering up for free use. They&rsquo;re called the open source community. They want you to take their stuff.</p>
<p><img src="wheels.png" alt="A comic I drew about using other people&rsquo;s stuff, with the wheel as an example."></p>
<p>Besides existing without you having to lift a finger, open source tools and software have some distinct advantages. Especially in the case of well-established projects, it&rsquo;s highly likely that someone else has already worked out all the most annoying bugs for you. Thanks to the ease with which users can view and modify source code, it&rsquo;s also more likely that a program has been tinkered with, improved, and secured over time. When many developers contribute, they bring their own unique expertise and experiences. This can result in a product far more robust and capable than one a single developer can produce.</p>
<p>Of course, being as varied as the people who build them, not all open source projects are created equal, nor maintained to be equally secure. There are many factors that affect a project&rsquo;s suitability for your use case. Here are a few general considerations that make a good starting point when choosing an open source project.</p>
<h2 id="how-to-choose-an-open-source-project">How to choose an open source project</h2>
<p>As its most basic requirements, a good software project is reliable, easy to understand, and has up-to-date components and security. There are several indicators that can help you make an educated guess about whether an open source project satisfies these criteria.</p>
<h3 id="whos-using-it">Who&rsquo;s using it</h3>
<p>Taken in context, the number of people already using an open source project may be indicative of how good it is. If a project has a hundred users, for instance, it stands to reason that someone has tried to use it at least a hundred times before you found it. Thus by the ancient customs of &ldquo;I don&rsquo;t know what&rsquo;s in that cave, you go first,&rdquo; it&rsquo;s more likely to be fine.</p>
<p>You can draw conclusions about a project&rsquo;s user base by looking at available statistics. Depending on your platform, these may include the number of downloads, reviews, issues or tickets, comments, contributions, forks, or &ldquo;stars,&rdquo; whatever those are.</p>
<p>Evaluate social statistics on platforms like GitHub with a grain of salt. They can help you determine how popular a project may be, but only in the same way that restaurant review apps can help you figure out if you should eat at Foo&rsquo;s Grill &amp; Bar. Depending on where Foo&rsquo;s Grill &amp; Bar is, when it opened, and how likely people are to be near it when the invariable steak craving should call, having twenty-six reviews may be a good sign or a terrible one. While you would not expect a project that addresses a very obscure use case or technology to have hundreds of users, having a few active users is, in such a case, just as confidence-inspiring.</p>
<p>External validation can also be useful. For example, packages that are included in a Linux operating system distribution (distro) must conform to stringent standards and undergo vetting. Choosing software that is included in a distro&rsquo;s default repositories can mean it&rsquo;s more likely to be secure.</p>
<p>Perhaps one of the best indications to look for is whether a project&rsquo;s development team is using their own project. Look for issues, discussions, or blog posts that show that the project&rsquo;s creators and maintainers are using what they&rsquo;ve built themselves. Commonly referred to as <a href="https://en.wikipedia.org/wiki/Eating_your_own_dog_food">&ldquo;eating your own dog food,&rdquo;</a> or &ldquo;dogfooding,&rdquo; it&rsquo;s an indicator that the project is most likely to be well-maintained by its developers.</p>
<h3 id="whos-building-it">Who&rsquo;s building it</h3>
<p>The main enemy of good open source software is usually a lack of interest. The parties involved in an open source project can make the difference between a flash-in-the-pan library and a respected long-term utility. Multiple committed maintainers, even making contributions in their spare time, have a much higher success rate of sustaining a project and generating interest.</p>
<p>Projects with healthy interest are usually supported by, and in turn cultivate, a community of contributors and users. New contributors may be actively welcomed, clear guides are available explaining how to help, and project maintainers are available and approachable when people have inevitable questions. Some communities even have chat rooms or forums where people can interact outside of contributions. Active communities help sustain project interest, relevance, and its ensuing quality.</p>
<p>In a less organic fashion, a project can also be sustained through organizations that sponsor it. Governments and companies with financial interest are open source patrons too, and a project that enjoys public sector use or financial backing has added incentive to remain relevant and useful.</p>
<h3 id="how-alive-is-it">How alive is it</h3>
<p>The recency and frequency of an open source project&rsquo;s activity is perhaps the best indicator of how much attention is likely paid to its security. Look at releases, commit history, changelogs, or documentation revisions to determine if a project is active. As projects vary in size and scope, here are some general things to look for.</p>
<p>Maintaining security is an ongoing endeavor that requires regular monitoring and updates, especially for projects with third-party components. These may be libraries or any part of the project that relies on something outside itself, such as a payment gateway integration. An inactive project is more likely to have outdated code or use outdated versions of components. For a more concrete determination, you can research a project&rsquo;s third-party components and compare their most recent patches or updates with the project&rsquo;s last updates.</p>
<p>Projects without third-party components may have no outside updates to apply. In these cases, you can use recent activity and release notes to determine how committed a project&rsquo;s maintainers may be. Generally, active projects should show updates within the last months, with a notable release within the last year. This can be a good indication of whether the project is using an up-to-date version of its language or framework.</p>
<p>You can also judge how active a project may be by looking at the project maintainers themselves. Active maintainers quickly respond to feedback or new issues, even if it&rsquo;s just to say, &ldquo;We&rsquo;re on it.&rdquo; If the project has a community, its maintainers are a part of it. They may have a dedicated website or write regular blogs. They may offer ways to contact them directly and privately, especially to raise security concerns.</p>
<h3 id="can-you-understand-it">Can you understand it</h3>
<p>Having documentation is a baseline requirement for a project that&rsquo;s intended for anyone but its creator to use. Good open source projects have documentation that is easy to follow, honest, and thorough.</p>
<p>Having <a href="/posts/word-bugs-in-software-documentation-and-how-to-fix-them/">well-written documentation</a> is one way a project can stand out and demonstrate the thoughtfulness and dedication of its maintainers. A &ldquo;Getting Started&rdquo; section may detail all the requirements and initial set up for running the project. An accurate list of topics in the documentation enables users to quickly find the information they need. A clear license statement leaves no doubt as to how the project can be used, and for what purposes. These are characteristic aspects of documentation that serves its users.</p>
<p>A project that is following sound coding practices likely has code that is as readable as its documentation. Code that is easy to read lends itself to being understood. Generally, it has clearly defined and appropriately-named functions and variables, a logical flow, and apparent purpose. Readable code is easier to fix, secure, and build upon.</p>
<h3 id="how-compatible-is-it">How compatible is it</h3>
<p>A few factors will determine how compatible a project is with your goals. These are objective qualities, and can be determined by looking at a project&rsquo;s repository files. They include:</p>
<ul>
<li>Code language</li>
<li>Specific technologies or frameworks</li>
<li>License compatibility</li>
</ul>
<p>Compatibility doesn&rsquo;t necessarily mean a direct match. Different code languages can interact with each other, as can various technologies and frameworks. You should carefully read a project&rsquo;s license to understand if it permits usage for your goal, or if it is compatible with a license you would like to use.</p>
<p>Ultimately, a project that satisfies all these criteria may still not quite suit your use case. Part of the beauty of open source software, however, is that you may still benefit from it by making alterations that better suit your usage. If those alterations make the project better for everyone, you can pay it back and pay it forward by contributing your work to the project.</p>
<h2 id="proper-care-and-feeding-of-an-open-source-project">Proper care and feeding of an open source project</h2>
<p>Once you adopt an open source project, a little attention is required to make sure it continues to be a boon to your goals. While its maintainers will look after the upstream project files, you alone are responsible for your own copy. Like all software, your open source project must be well-maintained in order to remain as secure and useful as possible.</p>
<p>Have a system that provides you with notifications when updates for your software are made available. Update software promptly, treating each patch as if it were vital to security; it may well be. Keep in mind that open source project creators and maintainers are, in most cases, acting only out of the goodness of their own hearts. If you&rsquo;ve got a particularly awesome one, its developers may make updates and security patches available on a regular basis. It&rsquo;s up to you to keep tabs on updates and promptly apply them.</p>
<p>As with most things in software, keeping your open source additions modular can come in handy. You might use <a href="https://git-scm.com/book/en/v2/Git-Tools-Submodules">git submodules</a>, branches, or environments to isolate your additions. This can make it easier to apply updates or pinpoint the source of any bugs that arise.</p>
<p>So although an open source project may cost no money, <em>caveat emptor,</em> which means, &ldquo;Jimmy, if we get you a puppy, it&rsquo;s your responsibility to take care of it.&rdquo;</p>
]]></content></entry><entry><title type="html">If you want to build a treehouse, start at the bottom</title><link href="https://victoria.dev/archive/if-you-want-to-build-a-treehouse-start-at-the-bottom/"/><id>https://victoria.dev/archive/if-you-want-to-build-a-treehouse-start-at-the-bottom/</id><author><name>Victoria Drake</name></author><published>2020-05-11T05:46:47-04:00</published><updated>2020-05-11T05:46:47-04:00</updated><content type="html"><![CDATA[<p>If you&rsquo;ve ever watched a kid draw a treehouse, you have some idea of how applications are built when security isn&rsquo;t made a priority. It&rsquo;s far more fun to draw the tire swing, front porch, and swimming pool than to worry about how a ten-thousand-gallon bucket of water stays suspended in midair. With too much attention spent on fun and flashy features, foundations suffer.</p>
<p><img src="for-the-turrets.png" alt="A comic I drew about building castles with poor foundations. It&rsquo;s not that funny."></p>
<p>Of course, spending undue hours building a back end like Fort Knox may not be necessary for your application, either. Being an advocate for security doesn&rsquo;t mean always wearing your tinfoil hat (although you do look dashing in it) but does mean building in an appropriate amount of security.</p>
<p>How much security is appropriate? The answer, frustratingly, is, &ldquo;it depends.&rdquo; The right amount of security for your application depends on who&rsquo;s using it, what it does, and most importantly, what undesirable things it could be made to do. It takes some analysis to make decisions about the kinds of risks your application faces and how you&rsquo;ll prepare to handle them. Okay, now&rsquo;s a good time to don your tinfoil hat. Let&rsquo;s imagine the worst.</p>
<h2 id="threat-modeling-whats-the-worst-that-could-happen">Threat modeling: what&rsquo;s the worst that could happen</h2>
<p>A <em>threat model</em> is a stuffy term for the result of trying to imagine the worst things that could happen to an application. Using your imagination to assess risks (fittingly called <em>risk assessment</em>) is a conveniently non-destructive method for finding ways an application can be attacked. You won&rsquo;t need any tools; just an understanding of how the application might work, and a little imagination. You&rsquo;ll want to record your results with pen and paper. For the younger folks, that means the notes app on your phone.</p>
<p>A few different methodologies for application risk assessment can be found in the software world, including the in-depth <a href="https://csrc.nist.gov/publications/detail/sp/800-30/rev-1/final">NIST Special Publication 800-30</a>. Each method&rsquo;s framework has specific steps and output, and will go into various levels of detail when it comes to defining threats. If following a framework, first choose the one you&rsquo;re most likely to complete. You can always add more depth and detail from there.</p>
<p>Even informal risk assessments are beneficial. Typically taking the form of a set of questions, they may be oriented around possible threats, the impact to assets, or ways a vulnerability could be exploited. Here are some examples of questions addressing each orientation:</p>
<ul>
<li>What kind of adversary would want to break my app? What would they be after?</li>
<li>If the control of <em>x</em> fell into the wrong hands, what could an attacker do with it?</li>
<li>Where could a <em>x</em> vulnerability occur in my app?</li>
</ul>
<p>A basic threat model explains the technical, business, and human considerations for each risk. It will typically detail:</p>
<ul>
<li>The vulnerabilities or components that can cause the risk</li>
<li>The impact that a successful execution of the risk would have on the application</li>
<li>The consequences for the application&rsquo;s users or organization</li>
</ul>
<p>The result of a risk assessment exercise is your threat model; in other words, a list of things you would very much like not to occur. It is usually sorted in a hierarchy of risks, from the worst to the mildest. The worst risks have the most negative impact, and are most important to protect against. The mildest risks are the most acceptable - while still an undesirable outcome, they have the least negative impact on the application and users.</p>
<p>You can use this resulting hierarchy as a guide to determine how much of your cybersecurity efforts to apply to each risk area. An appropriate amount of security for your application will eliminate (where possible) or mitigate the worst risks.</p>
<h2 id="pushing-left">Pushing left</h2>
<p>Although it sounds like a dance move meme, <em>pushing left</em> refers instead to building in as much of your planned security as possible in the early stages of software development.</p>
<p>Building software is a lot like building a treehouse, just without the pleasant fresh air. You start with the basic supporting components, such as attaching a platform to a tree. Then comes the framing, walls, and roof, and finally, your rustic-modern Instagram-worthy wall hangings and deer bust.</p>
<p>The further along in the build process you are, the harder and more costly it becomes to make changes to a component that you&rsquo;ve already installed. If you discover a problem with the walls only after the roof is put in place, you may need to change or remove the roof in order to fix it. Similar parallels can be drawn for software components, only without similar ease in detangling the attached parts.</p>
<p>In the case of a treehouse, it&rsquo;s rather impossible to start with decorations or even a roof, since you can&rsquo;t really suspend them in midair. In the case of software development, it is, unfortunately, possible to build many top-layer components and abstractions without a sufficient supporting architecture. A push-left approach views each additional layer as adding cost and complication. Pushing left means attempting to mitigate security risks as much as possible at each development stage before proceeding to the next.</p>
<h2 id="building-bottom-to-top">Building bottom-to-top</h2>
<p>By considering your threat model in the early stages of developing your application, you reduce the chances of necessitating a costly remodel later on. You can make choices about architecture, components, and code that support the main security goals of your particular application.</p>
<p>While it&rsquo;s not possible to foresee all the functionality your application may one day need to support, it is possible to prepare a solid foundation that allows additional functionality to be added more securely. Building in appropriate security from the bottom to the top will help make mitigating security risks much easier in the future.</p>
]]></content></entry><entry><title type="html">Hugo vs Jekyll: an epic battle of static site generator themes</title><link href="https://victoria.dev/archive/hugo-vs-jekyll-an-epic-battle-of-static-site-generator-themes/"/><id>https://victoria.dev/archive/hugo-vs-jekyll-an-epic-battle-of-static-site-generator-themes/</id><author><name>Victoria Drake</name></author><published>2020-04-27T06:34:41-04:00</published><updated>2020-04-27T06:34:41-04:00</updated><content type="html"><![CDATA[<p>I recently took on the task of creating a documentation site theme for two projects. Both projects needed the same basic features, but one uses Jekyll while the other uses Hugo.</p>
<p>In typical developer rationality, there was clearly only one option. I decided to create the same theme in both frameworks, and to give you, dear reader, a side-by-side comparison.</p>
<p>This post isn&rsquo;t a comprehensive theme-building guide, but intended to familiarize you with the process of building a theme in either generator. Here&rsquo;s what we&rsquo;ll cover:</p>
<ul>
<li><a href="#how-theme-files-are-organized">How theme files are organized</a></li>
<li><a href="#where-to-put-content">Where to put content</a></li>
<li><a href="#how-templating-works">How templating works</a></li>
<li><a href="#creating-a-top-level-menu-with-the-pages-object">Creating a top-level menu with the <code>pages</code> object</a></li>
<li><a href="#creating-a-menu-with-nested-links-from-a-data-list">Creating a menu with nested links from a data list</a></li>
<li><a href="#putting-the-template-together">Putting the template together</a></li>
<li><a href="#create-a-stylesheet">Create a stylesheet</a>
<ul>
<li><a href="#sass-and-css-in-jekyll">Sass and CSS in Jekyll</a></li>
<li><a href="#sass-and-hugo-pipes-in-hugo">Sass and Hugo Pipes in Hugo</a></li>
</ul>
</li>
<li><a href="#configure-and-deploy-to-github-pages">Configure and deploy to GitHub Pages</a>
<ul>
<li><a href="#configure-jekyll">Configure Jekyll</a></li>
<li><a href="#configure-hugo">Configure Hugo</a></li>
<li><a href="#deploy-to-github-pages">Deploy to GitHub Pages</a></li>
</ul>
</li>
<li><a href="#showtime">Showtime</a></li>
<li><a href="#wait-who-won">Wait who won</a></li>
</ul>
<p>Here&rsquo;s a crappy wireframe of the theme I&rsquo;m going to create.</p>
<p><img src="wireframe.jpg" alt="A sketch of the finished page"></p>
<p>If you&rsquo;re planning to build-along, it may be helpful to serve the theme locally as you build it; both generators offer this functionality. For Jekyll, run <code>jekyll serve</code>, and for Hugo, <code>hugo serve</code>.</p>
<p>There are two main elements: the main content area, and the all-important sidebar menu. To create them, you&rsquo;ll need template files that tell the site generator how to generate the HTML page. To organize theme template files in a sensible way, you first need to know what directory structure the site generator expects.</p>
<h2 id="how-theme-files-are-organized">How theme files are organized</h2>
<p>Jekyll supports gem-based themes, which users can install like any other Ruby gems. This method hides theme files in the gem, so for the purposes of this comparison, we aren&rsquo;t using gem-based themes.</p>
<p>When you run <code>jekyll new-theme &lt;name&gt;</code>, Jekyll will scaffold a new theme for you. Here&rsquo;s what those files look like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>.
</span></span><span style="display:flex;"><span>├── assets
</span></span><span style="display:flex;"><span>├── Gemfile
</span></span><span style="display:flex;"><span>├── _includes
</span></span><span style="display:flex;"><span>├── _layouts
</span></span><span style="display:flex;"><span>│   ├── default.html
</span></span><span style="display:flex;"><span>│   ├── page.html
</span></span><span style="display:flex;"><span>│   └── post.html
</span></span><span style="display:flex;"><span>├── LICENSE.txt
</span></span><span style="display:flex;"><span>├── README.md
</span></span><span style="display:flex;"><span>├── _sass
</span></span><span style="display:flex;"><span>└── &lt;name&gt;.gemspec
</span></span></code></pre></div><p>The directory names are appropriately descriptive. The <code>_includes</code> directory is for small bits of code that you reuse in different places, in much the same way you&rsquo;d put butter on everything. (Just me?) The <code>_layouts</code> directory contains templates for different types of pages on your site. The <code>_sass</code> folder is for <a href="https://sass-lang.com/documentation/syntax">Sass</a> files used to build your site&rsquo;s stylesheet.</p>
<p>You can scaffold a new Hugo theme by running <code>hugo new theme &lt;name&gt;</code>. It has these files:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>.
</span></span><span style="display:flex;"><span>├── archetypes
</span></span><span style="display:flex;"><span>│   └── default.md
</span></span><span style="display:flex;"><span>├── layouts
</span></span><span style="display:flex;"><span>│   ├── 404.html
</span></span><span style="display:flex;"><span>│   ├── _default
</span></span><span style="display:flex;"><span>│   │   ├── baseof.html
</span></span><span style="display:flex;"><span>│   │   ├── list.html
</span></span><span style="display:flex;"><span>│   │   └── single.html
</span></span><span style="display:flex;"><span>│   ├── index.html
</span></span><span style="display:flex;"><span>│   └── partials
</span></span><span style="display:flex;"><span>│       ├── footer.html
</span></span><span style="display:flex;"><span>│       ├── header.html
</span></span><span style="display:flex;"><span>│       └── head.html
</span></span><span style="display:flex;"><span>├── LICENSE
</span></span><span style="display:flex;"><span>├── static
</span></span><span style="display:flex;"><span>│   ├── css
</span></span><span style="display:flex;"><span>│   └── js
</span></span><span style="display:flex;"><span>└── theme.toml
</span></span></code></pre></div><p>You can see some similarities. Hugo&rsquo;s page template files are tucked into <code>layouts/</code>. Note that the <code>_default</code> page type has files for a <code>list.html</code> and a <code>single.html</code>. Unlike Jekyll, Hugo uses these specific file names to distinguish between <a href="https://gohugo.io/templates/lists/">list pages</a> (like a page with links to all your blog posts on it) and single pages (like one of your blog posts). The <code>layouts/partials/</code> directory contains the buttery reusable bits, and stylesheet files have a spot picked out in <code>static/css/</code>.</p>
<p>These directory structures aren&rsquo;t set in stone, as both site generators allow some measure of customization. For example, Jekyll lets you define <a href="https://jekyllrb.com/docs/collections/">collections</a>, and Hugo makes use of <a href="https://gohugo.io/content-management/page-bundles/">page bundles</a>. These features let you organize your content multiple ways, but for now, lets look at where to put some simple pages.</p>
<h2 id="where-to-put-content">Where to put content</h2>
<p>To create a site menu that looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-md" data-lang="md"><span style="display:flex;"><span>Introduction
</span></span><span style="display:flex;"><span>    Getting Started
</span></span><span style="display:flex;"><span>    Configuration
</span></span><span style="display:flex;"><span>    Deploying
</span></span><span style="display:flex;"><span>Advanced Usage
</span></span><span style="display:flex;"><span>    All Configuration Settings
</span></span><span style="display:flex;"><span>    Customizing
</span></span><span style="display:flex;"><span>    Help and Support
</span></span></code></pre></div><p>You&rsquo;ll need two sections (&ldquo;Introduction&rdquo; and &ldquo;Advanced Usage&rdquo;) containing their respective subsections.</p>
<p>Jekyll isn&rsquo;t strict with its content location. It expects pages in the root of your site, and will build whatever&rsquo;s there. Here&rsquo;s how you might organize these pages in your Jekyll site root:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>.
</span></span><span style="display:flex;"><span>├── 404.html
</span></span><span style="display:flex;"><span>├── assets
</span></span><span style="display:flex;"><span>├── Gemfile
</span></span><span style="display:flex;"><span>├── _includes
</span></span><span style="display:flex;"><span>├── index.markdown
</span></span><span style="display:flex;"><span>├── intro
</span></span><span style="display:flex;"><span>│   ├── config.md
</span></span><span style="display:flex;"><span>│   ├── deploy.md
</span></span><span style="display:flex;"><span>│   ├── index.md
</span></span><span style="display:flex;"><span>│   └── quickstart.md
</span></span><span style="display:flex;"><span>├── _layouts
</span></span><span style="display:flex;"><span>│   ├── default.html
</span></span><span style="display:flex;"><span>│   ├── page.html
</span></span><span style="display:flex;"><span>│   └── post.html
</span></span><span style="display:flex;"><span>├── LICENSE.txt
</span></span><span style="display:flex;"><span>├── README.md
</span></span><span style="display:flex;"><span>├── _sass
</span></span><span style="display:flex;"><span>├── &lt;name&gt;.gemspec
</span></span><span style="display:flex;"><span>└── usage
</span></span><span style="display:flex;"><span>    ├── customizing.md
</span></span><span style="display:flex;"><span>    ├── index.md
</span></span><span style="display:flex;"><span>    ├── settings.md
</span></span><span style="display:flex;"><span>    └── support.md
</span></span></code></pre></div><p>You can change the location of the site source in your <a href="https://jekyllrb.com/docs/configuration/default/">Jekyll configuration</a>.</p>
<p>In Hugo, all rendered content is expected in the <code>content/</code> folder. This prevents Hugo from trying to render pages you don&rsquo;t want, such as <code>404.html</code>, as site content. Here&rsquo;s how you might organize your <code>content/</code> directory in Hugo:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>.
</span></span><span style="display:flex;"><span>├── _index.md
</span></span><span style="display:flex;"><span>├── intro
</span></span><span style="display:flex;"><span>│   ├── config.md
</span></span><span style="display:flex;"><span>│   ├── deploy.md
</span></span><span style="display:flex;"><span>│   ├── _index.md
</span></span><span style="display:flex;"><span>│   └── quickstart.md
</span></span><span style="display:flex;"><span>└── usage
</span></span><span style="display:flex;"><span>    ├── customizing.md
</span></span><span style="display:flex;"><span>    ├── _index.md
</span></span><span style="display:flex;"><span>    ├── settings.md
</span></span><span style="display:flex;"><span>    └── support.md
</span></span></code></pre></div><p>To Hugo, <code>_index.md</code> and <code>index.md</code> mean different things. It can be helpful to know what kind of <a href="https://gohugo.io/content-management/page-bundles/">Page Bundle</a> you want for each section: Leaf, which has no children, or Branch.</p>
<p>Now that you have some idea of where to put things, let&rsquo;s look at how to build a page template.</p>
<h2 id="how-templating-works">How templating works</h2>
<p>Jekyll page templates are built with the <a href="https://jekyllrb.com/docs/liquid/">Liquid templating language</a>. It uses braces to output variable content to a page, such as the page&rsquo;s title: <code>{{ page.title }}</code>.</p>
<p>Hugo&rsquo;s templates also use braces, but they&rsquo;re built with <a href="https://gohugo.io/templates/introduction/">Go Templates</a>. The syntax is similar, but different: <code>{{ .Title }}</code>.</p>
<p>Both Liquid and Go Templates can handle logic. Liquid uses <em>tags</em> syntax to denote logic operations:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>{% if user %}
</span></span><span style="display:flex;"><span>  Hello {{ user.name }}!
</span></span><span style="display:flex;"><span>{% endif %}
</span></span></code></pre></div><p>And Go Templates places its functions and arguments in its braces syntax:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>{{ <span style="color:#66d9ef">if</span> .<span style="color:#a6e22e">User</span> }}
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Hello</span> {{ .<span style="color:#a6e22e">User</span> }}!
</span></span><span style="display:flex;"><span>{{ <span style="color:#a6e22e">end</span> }}
</span></span></code></pre></div><p>Templating languages allow you to build one skeleton HTML page, then tell the site generator to put variable content in areas you define. Let&rsquo;s compare two possible <code>default</code> page templates for Jekyll and Hugo.</p>
<p>Jekyll&rsquo;s scaffold <code>default</code> theme is bare, so we&rsquo;ll look at their starter theme <a href="https://github.com/jekyll/minima">Minima</a>. Here&rsquo;s <code>_layouts/default.html</code> in Jekyll (Liquid):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span><span style="color:#75715e">&lt;!DOCTYPE html&gt;</span>
</span></span><span style="display:flex;"><span>&lt;<span style="color:#f92672">html</span> <span style="color:#a6e22e">lang</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;{{ page.lang | default: site.lang | default: &#34;</span><span style="color:#a6e22e">en</span><span style="color:#960050;background-color:#1e0010">&#34;</span> <span style="color:#960050;background-color:#1e0010">}}&#34;</span>&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  {%- include head.html -%}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">body</span>&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    {%- include header.html -%}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">main</span> <span style="color:#a6e22e">class</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;page-content&#34;</span> <span style="color:#a6e22e">aria-label</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Content&#34;</span>&gt;
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#f92672">div</span> <span style="color:#a6e22e">class</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;wrapper&#34;</span>&gt;
</span></span><span style="display:flex;"><span>        {{ content }}
</span></span><span style="display:flex;"><span>      &lt;/<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">main</span>&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    {%- include footer.html -%}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  &lt;/<span style="color:#f92672">body</span>&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">html</span>&gt;
</span></span></code></pre></div><p>Here&rsquo;s Hugo&rsquo;s scaffold theme <code>layouts/_default/baseof.html</code> (Go Templates):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span><span style="color:#75715e">&lt;!DOCTYPE html&gt;</span>
</span></span><span style="display:flex;"><span>&lt;<span style="color:#f92672">html</span>&gt;
</span></span><span style="display:flex;"><span>    {{- partial &#34;head.html&#34; . -}}
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">body</span>&gt;
</span></span><span style="display:flex;"><span>        {{- partial &#34;header.html&#34; . -}}
</span></span><span style="display:flex;"><span>        &lt;<span style="color:#f92672">div</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;content&#34;</span>&gt;
</span></span><span style="display:flex;"><span>        {{- block &#34;main&#34; . }}{{- end }}
</span></span><span style="display:flex;"><span>        &lt;/<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>        {{- partial &#34;footer.html&#34; . -}}
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">body</span>&gt;
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">html</span>&gt;
</span></span></code></pre></div><p>Different syntax, same idea. Both templates pull in reusable bits for <code>head.html</code>, <code>header.html</code>, and <code>footer.html</code>. These show up on a lot of pages, so it makes sense not to have to repeat yourself. Both templates also have a spot for the main content, though the Jekyll template uses a variable (<code>{{ content }}</code>) while Hugo uses a block (<code>{{- block &quot;main&quot; . }}{{- end }}</code>). Blocks are just another way Hugo lets you define reusable bits.</p>
<p>Now that you know how templating works, you can build the sidebar menu for the theme.</p>
<h2 id="creating-a-top-level-menu-with-the-pages-object">Creating a top-level menu with the <code>pages</code> object</h2>
<p>You can programmatically create a top-level menu from your pages. It will look like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-md" data-lang="md"><span style="display:flex;"><span>Introduction
</span></span><span style="display:flex;"><span>Advanced Usage
</span></span></code></pre></div><p>Let&rsquo;s start with Jekyll. You can display links to site pages in your Liquid template by iterating through the <code>site.pages</code> object that Jekyll provides and building a list:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#f92672">ul</span>&gt;
</span></span><span style="display:flex;"><span>    {% for page in site.pages %}
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">li</span>&gt;&lt;<span style="color:#f92672">a</span> <span style="color:#a6e22e">href</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;{{ page.url | absolute_url }}&#34;</span>&gt;{{ page.title }}&lt;/<span style="color:#f92672">a</span>&gt;&lt;/<span style="color:#f92672">li</span>&gt;
</span></span><span style="display:flex;"><span>    {% endfor %}
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">ul</span>&gt;
</span></span></code></pre></div><p>This returns all of the site&rsquo;s pages, including all the ones that you might not want, like <code>404.html</code>. You can filter for the pages you actually want with a couple more tags, such as conditionally including pages if they have a <code>section: true</code> parameter set:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#f92672">ul</span>&gt;
</span></span><span style="display:flex;"><span>    {% for page in site.pages %}
</span></span><span style="display:flex;"><span>    {%- if page.section -%}
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">li</span>&gt;&lt;<span style="color:#f92672">a</span> <span style="color:#a6e22e">href</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;{{ page.url | absolute_url }}&#34;</span>&gt;{{ page.title }}&lt;/<span style="color:#f92672">a</span>&gt;&lt;/<span style="color:#f92672">li</span>&gt;
</span></span><span style="display:flex;"><span>    {%- endif -%}
</span></span><span style="display:flex;"><span>    {% endfor %}
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">ul</span>&gt;
</span></span></code></pre></div><p>You can achieve the same effect with slightly less code in Hugo. Loop through Hugo&rsquo;s <code>.Pages</code> object using Go Template&rsquo;s <a href="https://golang.org/pkg/text/template/#hdr-Actions"><code>range</code> action</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#f92672">ul</span>&gt;
</span></span><span style="display:flex;"><span>{{ range .Pages }}
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">li</span>&gt;
</span></span><span style="display:flex;"><span>        &lt;<span style="color:#f92672">a</span> <span style="color:#a6e22e">href</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;{{.Permalink}}&#34;</span>&gt;{{.Title}}&lt;/<span style="color:#f92672">a</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">li</span>&gt;
</span></span><span style="display:flex;"><span>{{ end }}
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">ul</span>&gt;
</span></span></code></pre></div><p>This template uses the <code>.Pages</code> object to return all the top-level pages in <code>content/</code> of your Hugo site. Since Hugo uses a specific folder for the site content you want rendered, there&rsquo;s no additional filtering necessary to build a simple menu of site pages.</p>
<h2 id="creating-a-menu-with-nested-links-from-a-data-list">Creating a menu with nested links from a data list</h2>
<p>Both site generators can use a separately defined data list of links to render a menu in your template. This is more suitable for creating nested links, like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-md" data-lang="md"><span style="display:flex;"><span>Introduction
</span></span><span style="display:flex;"><span>    Getting Started
</span></span><span style="display:flex;"><span>    Configuration
</span></span><span style="display:flex;"><span>    Deploying
</span></span><span style="display:flex;"><span>Advanced Usage
</span></span><span style="display:flex;"><span>    All Configuration Settings
</span></span><span style="display:flex;"><span>    Customizing
</span></span><span style="display:flex;"><span>    Help and Support
</span></span></code></pre></div><p>Jekyll supports <a href="https://jekyllrb.com/docs/datafiles/">data files</a> in a few formats, including YAML. Here&rsquo;s the definition for the menu above in <code>_data/menu.yml</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yml" data-lang="yml"><span style="display:flex;"><span><span style="color:#f92672">section</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">page</span>: <span style="color:#ae81ff">Introduction</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">url</span>: <span style="color:#ae81ff">/intro</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">subsection</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">page</span>: <span style="color:#ae81ff">Getting Started</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">url</span>: <span style="color:#ae81ff">/intro/quickstart</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">page</span>: <span style="color:#ae81ff">Configuration</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">url</span>: <span style="color:#ae81ff">/intro/config</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">page</span>: <span style="color:#ae81ff">Deploying</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">url</span>: <span style="color:#ae81ff">/intro/deploy</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">page</span>: <span style="color:#ae81ff">Advanced Usage</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">url</span>: <span style="color:#ae81ff">/usage</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">subsection</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">page</span>: <span style="color:#ae81ff">Customizing</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">url</span>: <span style="color:#ae81ff">/usage/customizing</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">page</span>: <span style="color:#ae81ff">All Configuration Settings</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">url</span>: <span style="color:#ae81ff">/usage/settings</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">page</span>: <span style="color:#ae81ff">Help and Support</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">url</span>: <span style="color:#ae81ff">/usage/support</span>
</span></span></code></pre></div><p>Here&rsquo;s how to render the data in the sidebar template:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>{% for a in site.data.menu.section %}
</span></span><span style="display:flex;"><span>&lt;<span style="color:#f92672">a</span> <span style="color:#a6e22e">href</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;{{ a.url }}&#34;</span>&gt;{{ a.page }}&lt;/<span style="color:#f92672">a</span>&gt;
</span></span><span style="display:flex;"><span>&lt;<span style="color:#f92672">ul</span>&gt;
</span></span><span style="display:flex;"><span>    {% for b in a.subsection %}
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">li</span>&gt;&lt;<span style="color:#f92672">a</span> <span style="color:#a6e22e">href</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;{{ b.url }}&#34;</span>&gt;{{ b.page }}&lt;/<span style="color:#f92672">a</span>&gt;&lt;/<span style="color:#f92672">li</span>&gt;
</span></span><span style="display:flex;"><span>    {% endfor %}
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">ul</span>&gt;
</span></span><span style="display:flex;"><span>{% endfor %}
</span></span></code></pre></div><p>This method allows you to build a custom menu, two nesting levels deep. The nesting levels are limited by the <code>for</code> loops in the template. For a recursive version that handles further levels of nesting, see <a href="https://jekyllrb.com/tutorials/navigation/#scenario-9-nested-tree-navigation-with-recursion">Nested tree navigation with recursion</a>.</p>
<p>Hugo does something similar with its <a href="https://gohugo.io/templates/menu-templates/#section-menu-for-lazy-bloggers">menu templates</a>. You can define menu links in your <a href="https://gohugo.io/getting-started/configuration/">Hugo site config</a>, and even add useful properties that Hugo understands, like weighting. Here&rsquo;s a definition of the menu above in <code>config.yaml</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yml" data-lang="yml"><span style="display:flex;"><span><span style="color:#f92672">sectionPagesMenu</span>: <span style="color:#ae81ff">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">menu</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">main</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">identifier</span>: <span style="color:#ae81ff">intro</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Introduction</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">url</span>: <span style="color:#ae81ff">/intro/</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">weight</span>: <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Getting Started</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">parent</span>: <span style="color:#ae81ff">intro</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">url</span>: <span style="color:#ae81ff">/intro/quickstart/</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">weight</span>: <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Configuration</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">parent</span>: <span style="color:#ae81ff">intro</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">url</span>: <span style="color:#ae81ff">/intro/config/</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">weight</span>: <span style="color:#ae81ff">2</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Deploying</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">parent</span>: <span style="color:#ae81ff">intro</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">url</span>: <span style="color:#ae81ff">/intro/deploy/</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">weight</span>: <span style="color:#ae81ff">3</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">identifier</span>: <span style="color:#ae81ff">usage</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Advanced Usage</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">url</span>: <span style="color:#ae81ff">/usage/</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Customizing</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">parent</span>: <span style="color:#ae81ff">usage</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">url</span>: <span style="color:#ae81ff">/usage/customizing/</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">weight</span>: <span style="color:#ae81ff">2</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">All Configuration Settings</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">parent</span>: <span style="color:#ae81ff">usage</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">url</span>: <span style="color:#ae81ff">/usage/settings/</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">weight</span>: <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Help and Support</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">parent</span>: <span style="color:#ae81ff">usage</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">url</span>: <span style="color:#ae81ff">/usage/support/</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">weight</span>: <span style="color:#ae81ff">3</span>
</span></span></code></pre></div><p>Hugo uses the <code>identifier</code>, which must match the section name, along with the <code>parent</code> variable to handle nesting. Here&rsquo;s how to render the menu in the sidebar template:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#f92672">ul</span>&gt;
</span></span><span style="display:flex;"><span>    {{ range .Site.Menus.main }}
</span></span><span style="display:flex;"><span>    {{ if .HasChildren }}
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">li</span>&gt;
</span></span><span style="display:flex;"><span>        &lt;<span style="color:#f92672">a</span> <span style="color:#a6e22e">href</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;{{ .URL }}&#34;</span>&gt;{{ .Name }}&lt;/<span style="color:#f92672">a</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">li</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">ul</span> <span style="color:#a6e22e">class</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;sub-menu&#34;</span>&gt;
</span></span><span style="display:flex;"><span>        {{ range .Children }}
</span></span><span style="display:flex;"><span>        &lt;<span style="color:#f92672">li</span>&gt;
</span></span><span style="display:flex;"><span>            &lt;<span style="color:#f92672">a</span> <span style="color:#a6e22e">href</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;{{ .URL }}&#34;</span>&gt;{{ .Name }}&lt;/<span style="color:#f92672">a</span>&gt;
</span></span><span style="display:flex;"><span>        &lt;/<span style="color:#f92672">li</span>&gt;
</span></span><span style="display:flex;"><span>        {{ end }}
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">ul</span>&gt;
</span></span><span style="display:flex;"><span>    {{ else }}
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">li</span>&gt;
</span></span><span style="display:flex;"><span>        &lt;<span style="color:#f92672">a</span> <span style="color:#a6e22e">href</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;{{ .URL }}&#34;</span>&gt;{{ .Name }}&lt;/<span style="color:#f92672">a</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">li</span>&gt;
</span></span><span style="display:flex;"><span>    {{ end }}
</span></span><span style="display:flex;"><span>    {{ end }}
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">ul</span>&gt;
</span></span></code></pre></div><p>The <code>range</code> function iterates over the menu data, and Hugo&rsquo;s <code>.Children</code> variable handles nested pages for you.</p>
<h2 id="putting-the-template-together">Putting the template together</h2>
<p>With your menu in your reusable sidebar bit (<code>_includes/sidebar.html</code> for Jekyll and <code>partials/sidebar.html</code> for Hugo), you can add it to the <code>default.html</code> template.</p>
<p>In Jekyll:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span><span style="color:#75715e">&lt;!DOCTYPE html&gt;</span>
</span></span><span style="display:flex;"><span>&lt;<span style="color:#f92672">html</span> <span style="color:#a6e22e">lang</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;{{ page.lang | default: site.lang | default: &#34;</span><span style="color:#a6e22e">en</span><span style="color:#960050;background-color:#1e0010">&#34;</span> <span style="color:#960050;background-color:#1e0010">}}&#34;</span>&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>{%- include head.html -%}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>&lt;<span style="color:#f92672">body</span>&gt;
</span></span><span style="display:flex;"><span>    {%- include sidebar.html -%}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    {%- include header.html -%}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">div</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;content&#34;</span> <span style="color:#a6e22e">class</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;page-content&#34;</span> <span style="color:#a6e22e">aria-label</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Content&#34;</span>&gt;
</span></span><span style="display:flex;"><span>        {{ content }}
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    {%- include footer.html -%}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">body</span>&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">html</span>&gt;
</span></span></code></pre></div><p>In Hugo:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span><span style="color:#75715e">&lt;!DOCTYPE html&gt;</span>
</span></span><span style="display:flex;"><span>&lt;<span style="color:#f92672">html</span>&gt;
</span></span><span style="display:flex;"><span>{{- partial &#34;head.html&#34; . -}}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>&lt;<span style="color:#f92672">body</span>&gt;
</span></span><span style="display:flex;"><span>    {{- partial &#34;sidebar.html&#34; . -}}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    {{- partial &#34;header.html&#34; . -}}
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">div</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;content&#34;</span> <span style="color:#a6e22e">class</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;page-content&#34;</span> <span style="color:#a6e22e">aria-label</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Content&#34;</span>&gt;
</span></span><span style="display:flex;"><span>        {{- block &#34;main&#34; . }}{{- end }}
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>    {{- partial &#34;footer.html&#34; . -}}
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">body</span>&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">html</span>&gt;
</span></span></code></pre></div><p>When the site is generated, each page will contain all the code from your <code>sidebar.html</code>.</p>
<h2 id="create-a-stylesheet">Create a stylesheet</h2>
<p>Both site generators accept Sass for creating CSS stylesheets. Jekyll <a href="https://jekyllrb.com/docs/assets/">has Sass processing built in</a>, and Hugo uses <a href="https://gohugo.io/hugo-pipes/transform-to-css/">Hugo Pipes</a>. Both options have some quirks.</p>
<h3 id="sass-and-css-in-jekyll">Sass and CSS in Jekyll</h3>
<p>To process a Sass file in Jekyll, create your style definitions in the <code>_sass</code> directory. For example, in a file at <code>_sass/style-definitions.scss</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-scss" data-lang="scss"><span style="display:flex;"><span>$background-color<span style="color:#f92672">:</span> <span style="color:#ae81ff">#eef</span> <span style="color:#66d9ef">!default</span>;
</span></span><span style="display:flex;"><span>$text-color<span style="color:#f92672">:</span> <span style="color:#ae81ff">#111</span> <span style="color:#66d9ef">!default</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">body</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">background-color</span><span style="color:#f92672">:</span> $background-color;
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">color</span><span style="color:#f92672">:</span> $text-color;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Jekyll won&rsquo;t generate this file directly, as it only processes files with front matter. To create the end-result filepath for your site&rsquo;s stylesheet, use a placeholder with empty front matter where you want the <code>.css</code> file to appear. For example, <code>assets/css/style.scss</code>. In this file, simply import your styles:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-scss" data-lang="scss"><span style="display:flex;"><span><span style="color:#f92672">---</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">---</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">@</span><span style="color:#f92672">import</span> <span style="color:#e6db74">&#34;style-definitions&#34;</span>;
</span></span></code></pre></div><p>This rather hackish configuration has an upside: you can use Liquid template tags and variables in your placeholder file. This is a nice way to allow users to set variables from the site <code>_config.yml</code>, for example.</p>
<p>The resulting CSS stylesheet in your generated site has the path <code>/assets/css/style.css</code>. You can link to it in your site&rsquo;s <code>head.html</code> using:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#f92672">link</span> <span style="color:#a6e22e">rel</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;stylesheet&#34;</span> <span style="color:#a6e22e">href</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;{{ &#34;</span><span style="color:#960050;background-color:#1e0010">/</span><span style="color:#a6e22e">assets</span><span style="color:#960050;background-color:#1e0010">/</span><span style="color:#a6e22e">css</span><span style="color:#960050;background-color:#1e0010">/</span><span style="color:#a6e22e">style</span><span style="color:#960050;background-color:#1e0010">.</span><span style="color:#a6e22e">css</span><span style="color:#960050;background-color:#1e0010">&#34;</span> <span style="color:#960050;background-color:#1e0010">|</span> <span style="color:#a6e22e">relative_url</span> <span style="color:#960050;background-color:#1e0010">}}&#34;</span> <span style="color:#a6e22e">media</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;screen&#34;</span>&gt;
</span></span></code></pre></div><h3 id="sass-and-hugo-pipes-in-hugo">Sass and Hugo Pipes in Hugo</h3>
<p>Hugo uses <a href="https://gohugo.io/hugo-pipes/transform-to-css/">Hugo Pipes</a> to process Sass to CSS. You can achieve this by using Hugo&rsquo;s asset processing function, <code>resources.ToCSS</code>, which expects a source in the <code>assets/</code> directory. It takes the SCSS file as an argument. With your style definitions in a Sass file at <code>assets/sass/style.scss</code>, here&rsquo;s how to get, process, and link your Sass in your theme&rsquo;s <code>head.html</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>{{ $style := resources.Get &#34;/sass/style.scss&#34; | resources.ToCSS }}
</span></span><span style="display:flex;"><span>&lt;<span style="color:#f92672">link</span> <span style="color:#a6e22e">rel</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;stylesheet&#34;</span> <span style="color:#a6e22e">href</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;{{ $style.RelPermalink }}&#34;</span> <span style="color:#a6e22e">media</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;screen&#34;</span>&gt;
</span></span></code></pre></div><p>Hugo asset processing <a href="https://gohugo.io/troubleshooting/faq/#i-get-tocss--this-feature-is-not-available-in-your-current-hugo-version">requires extended Hugo</a>, which you may not have by default. You can get extended Hugo from the <a href="https://github.com/gohugoio/hugo/releases">releases page</a>.</p>
<h2 id="configure-and-deploy-to-github-pages">Configure and deploy to GitHub Pages</h2>
<p>Before your site generator can build your site, it needs a configuration file to set some necessary parameters. Configuration files live in the site root directory. Among other settings, you can declare the name of the theme to use when building the site.</p>
<h3 id="configure-jekyll">Configure Jekyll</h3>
<p>Here&rsquo;s a minimal <code>_config.yml</code> for Jekyll:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yml" data-lang="yml"><span style="display:flex;"><span><span style="color:#f92672">title</span>: <span style="color:#ae81ff">Your awesome title</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">description</span>: <span style="color:#ae81ff">&gt;-</span> <span style="color:#75715e"># this means to ignore newlines until &#34;baseurl:&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ae81ff">Write an awesome description for your new site here. You can edit this</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ae81ff">line in _config.yml. It will appear in your document head meta (for</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ae81ff">Google search results) and in your feed.xml site description.</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">baseurl</span>: <span style="color:#e6db74">&#34;&#34;</span> <span style="color:#75715e"># the subpath of your site, e.g. /blog</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">url</span>: <span style="color:#e6db74">&#34;&#34;</span> <span style="color:#75715e"># the base hostname &amp; protocol for your site, e.g. http://example.com</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">theme</span>: <span style="color:#75715e"># for gem-based themes</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">remote_theme</span>: <span style="color:#75715e"># for themes hosted on GitHub, when used with GitHub Pages</span>
</span></span></code></pre></div><p>With <code>remote_theme</code>, any <a href="https://docs.github.com/en/pages/setting-up-a-github-pages-site-with-jekyll/adding-a-theme-to-your-github-pages-site-using-jekyll#adding-a-theme">Jekyll theme hosted on GitHub can be used</a> with sites hosted on GitHub Pages.</p>
<p>Jekyll has a <a href="https://jekyllrb.com/docs/configuration/default/">default configuration</a>, so any parameters added to your configuration file will override the defaults. Here are <a href="https://jekyllrb.com/docs/configuration/options/">additional configuration settings</a>.</p>
<h3 id="configure-hugo">Configure Hugo</h3>
<p>Here&rsquo;s a minimal example of Hugo&rsquo;s <code>config.yml</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yml" data-lang="yml"><span style="display:flex;"><span><span style="color:#f92672">baseURL</span>: <span style="color:#ae81ff">https://example.com/</span> <span style="color:#75715e"># The full domain your site will live at</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">languageCode</span>: <span style="color:#ae81ff">en-us</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">title</span>: <span style="color:#ae81ff">Hugo Docs Site</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">theme</span>: <span style="color:#75715e"># theme name</span>
</span></span></code></pre></div><p>Hugo makes no assumptions, so if a necessary parameter is missing, you&rsquo;ll see a warning when building or serving your site. Here are <a href="https://gohugo.io/getting-started/configuration/#all-configuration-settings">all configuration settings for Hugo</a>.</p>
<h3 id="deploy-to-github-pages">Deploy to GitHub Pages</h3>
<p>Both generators build your site with a command.</p>
<p>For Jekyll, use <code>jekyll build</code>. See <a href="https://jekyllrb.com/docs/configuration/options/#build-command-options">further build options here</a>.</p>
<p>For Hugo, use <code>hugo</code>. You can run <code>hugo help</code> or see <a href="https://gohugo.io/getting-started/usage/#test-installation">further build options here</a>.</p>
<p>You&rsquo;ll have to choose the source for your GitHub Pages site; once done, your site will update each time you push a new build. Of course, you can also automate your GitHub Pages build using GitHub Actions. Here&rsquo;s one for <a href="https://github.com/victoriadrake/hugo-latest-cd">building and deploying with Hugo</a>, and one for <a href="https://github.com/victoriadrake/jekyll-cd">building and deploying Jekyll</a>.</p>
<h2 id="showtime">Showtime</h2>
<p>All the substantial differences between these two generators are under the hood; all the same, let&rsquo;s take a look at the finished themes, in two color variations.</p>
<p>Here&rsquo;s Hugo:</p>
<p><img src="ogd_hugo.png" alt="OpenGitDocs theme for Hugo"></p>
<p>Here&rsquo;s Jekyll:</p>
<p><img src="ogd_jekyll.png" alt="OpenGitDocs theme for Jekyll"></p>
<p>Spiffy!</p>
<h2 id="wait-who-won">Wait who won</h2>
<p>🤷</p>
<p>Both Hugo and Jekyll have their quirks and conveniences.</p>
<p>From this developer&rsquo;s perspective, Jekyll is a workable choice for simple sites without complicated organizational needs. If you&rsquo;re looking to render some one-page posts in an <a href="https://jekyllrb.com/docs/themes/">available theme</a> and host with GitHub Pages, Jekyll will get you up and running fairly quickly.</p>
<p>Personally, I use Hugo. I like the organizational capabilities of its Page Bundles, and it&rsquo;s backed by a dedicated and conscientious team that really seems to strive to facilitate convenience for their users. This is evident in Hugo&rsquo;s many functions, and handy tricks like <a href="https://gohugo.io/content-management/image-processing/">Image Processing</a> and <a href="https://gohugo.io/content-management/shortcodes/">Shortcodes</a>. They seem to release new fixes and versions about as often as I make a new cup of coffee.</p>
<p>If you still can&rsquo;t decide, don&rsquo;t worry. Many themes are available for both Hugo and Jekyll! Start with one, switch later if you want. That&rsquo;s the benefit of having options.</p>
]]></content></entry><entry><title type="html">Outsourcing security with 1Password, Authy, and Privacy.com</title><link href="https://victoria.dev/archive/outsourcing-security-with-1password-authy-and-privacy.com/"/><id>https://victoria.dev/archive/outsourcing-security-with-1password-authy-and-privacy.com/</id><author><name>Victoria Drake</name></author><published>2020-03-16T08:12:32-04:00</published><updated>2020-03-16T08:12:32-04:00</updated><content type="html"><![CDATA[<p>We&rsquo;ve already got enough to deal with without worrying about our cybersecurity. When humans are busy and under stress, we tend to get lax in less-obviously-pressing areas, like the integrity of our online accounts. These areas only become an obvious problem when it&rsquo;s too late for prevention.</p>
<p>Cybersecurity can be fiddly and time-consuming. You might need to reset forgotten passwords, transfer multifactor authentication (MFA) codes to different devices, or deal with the fallout of compromised payment details in the event one of your accounts is still breached.</p>
<p>Thankfully, most of the work necessary to keep up our cybersecurity measures can be outsourced.</p>
<p>Here are three changes you can make to significantly reduce the chances of needing to fiddle with any of these things again.</p>
<h2 id="1password">1Password</h2>
<p><img src="1Password-iOS-FaceID-darkmode.jpg" alt="1Password on an iPhone"></p>
<p>I&rsquo;ve historically avoided password managers because of an irrational knee-jerk reaction to putting all my eggs in one basket. You know what&rsquo;s great for irrational reactions? Education.</p>
<p>To figure out if putting all my passwords into a password manager is more secure than not using one, I set out to see what some smart people wrote about it.</p>
<p>First, we need to know a thing or two about passwords. Troy Hunt figured out almost a decade ago that <a href="https://www.troyhunt.com/only-secure-password-is-one-you-cant/">trying to remember strong passwords doesn&rsquo;t work</a>. In more recent times, Alex Weinert expanded on this in <a href="https://techcommunity.microsoft.com/t5/azure-active-directory-identity/your-pa-word-doesn-t-matter/ba-p/731984">Your Pa$$word doesn&rsquo;t matter</a>. TL;DR: our brains aren&rsquo;t better at passwords than computers, and please use MFA.</p>
<p>So passwords don&rsquo;t matter, but complicated passwords are still better than memorable and guessable ones. Since I&rsquo;ve next to no hope of remembering a dozen variations of <code>p/q2-q4!</code> (I&rsquo;m not a <a href="https://inbox.vuxu.org/tuhs/CAG=a+rj8VcXjS-ftaj8P2_duLFSUpmNgB4-dYwnTsY_8g5WdEA@mail.gmail.com/">chess player</a>), this is a task I can outsource to <a href="https://1password.com/">1Password</a>. I&rsquo;ll still need to remember one, long, complicated master password - 1Password uses this to encrypt my data, so I really can&rsquo;t lose it - but I can handle just one.</p>
<p>Using 1Password specifically has another, decidedly obvious, advantage. I chose 1Password because of their <a href="https://support.1password.com/watchtower/">Watchtower</a> feature. <a href="https://www.troyhunt.com/have-i-been-pwned-is-now-partnering-with-1password/">Thanks to Troy Hunt&rsquo;s Have I Been Pwned</a>, Watchtower will alert you if any of your passwords show up in a breach so you can change them. Passwords still don&rsquo;t completely work, but this is probably the best band-aid there is.</p>
<p>One last bonus is that using a password manager is a heck of a lot more convenient. I don&rsquo;t need to take a few tries to type in a complicated password. I don&rsquo;t end up spending time resetting passwords I&rsquo;ve forgotten on sites I only rarely use.</p>
<p>When tasked with remembering all their own passwords, people typically create simpler passwords that are easier to remember &ndash; and easier to hack. This occurs most frequently on sites that are considered unimportant. Using 1Password and generated passwords, those sites are now also first-class citizens in the land of strong passwords, instead of being half-abandoned and half-open attack vectors.</p>
<p>So, yes, all my eggs are in one basket. A well-protected, complex, and monitored basket.</p>
<h2 id="authy">Authy</h2>
<p>Okay - so it&rsquo;s more like one-and-a-half baskets. 🤷🏻</p>
<p><img src="Authy2019Logo.png" alt="Authy&rsquo;s logo"></p>
<p><a href="https://authy.com/">Authy</a>, from the folks over at <a href="https://www.twilio.com">Twilio</a>, provides a 2FA solution that&rsquo;s more secure than SMS. <a href="https://www.twilio.com/en-us/blog/products/authy-vs-google-authenticator">Unlike Google Authenticator</a>, you can choose to back up your 2FA codes in case you lose or change your phone. (1Password offers 2FA functionality as well - but, you know, redundancies.)</p>
<p>With Authy, your back up is encrypted with your password, similarly to how 1Password works. This makes it the second password you can&rsquo;t forget, if you don&rsquo;t want to lose access to your codes. If you reset your account, they all go away. I can deal with remembering two passwords; I&rsquo;ll take that trade.</p>
<p>I&rsquo;ve tried other methods of MFA, including hardware keys, which can make accessing accounts on your phone more complicated than I care to put up with. I find the combination of 1Password and Authy to be the most practical combination of convenience and security that yet exists to my knowledge.</p>
<h2 id="privacycom">Privacy.com</h2>
<p><img src="privacy-ephemeral.png" alt="Screenshot of Privacy card"></p>
<p>Finally, there&rsquo;s one last line of defense you can put in place in the unfortunate event that one of your accounts is still compromised. All the strong passwords and MFA in the world won&rsquo;t help if you open the doors yourself, and scams and phishing are a thing.</p>
<p>Since it&rsquo;s rather impractical to use a different real credit card every place you shop, virtual cards are just a great idea. There&rsquo;s no good reason to spend an afternoon (or more) resetting your payment information on every account just to thwart a misbehaving merchant or patch up a data breach from that online shop for cute salt shakers you made a purchase at last year (just me?).</p>
<p>As a bonus, a <a href="https://blog.privacy.com/create-virtual-cards-with-privacy-and-1password/">partnership between 1Password and Privacy.com</a> lets you easily create virtual credit cards using the 1Password extension.</p>
<p>By setting up a separate virtual card for each merchant, in the event that one of those merchants is compromised, you can simply pause or delete that card. None of your other accounts or actual bank details are caught up in the process. Cards can have time-based limits or be one-off burner numbers, making them ideal for setting up subscriptions.</p>
<p>This is the sort of basic functionality that I hope, one day, becomes more prevalent from banks and credit cards. In the meantime, I&rsquo;ll keep using <a href="https://app.privacy.com/join/Q6V3V">Privacy.com</a>. That&rsquo;s my referral link; if you&rsquo;d like to thank me by using it, we&rsquo;ll both get five bucks as a bonus.</p>
<h2 id="outsource-better-security">Outsource better security</h2>
<p>All together, implementing these changes will probably take up an afternoon, depending on how many accounts you have. It&rsquo;s worth it for the time you&rsquo;d otherwise spend resetting passwords, setting up new devices, or (knock on wood) recovering from compromised banking details. Best of all, you&rsquo;ll have continual protection just running in the background.</p>
<ul>
<li><a href="https://1password.com/">1Password</a></li>
<li><a href="https://authy.com/">Authy</a></li>
<li><a href="https://app.privacy.com/join/Q6V3V">Privacy.com</a></li>
</ul>
<p>We have the technology. Free up some brain cycles to focus on other things - or simply remove some unnecessary stress from your life by outsourcing the fiddly bits.</p>
<p>Want to give the gift of cybersecurity to someone you know? Get them started with a <a href="/blog/your-cybersecurity-starter-pack/">cybersecurity starter pack</a>.</p>
]]></content></entry><entry><title type="html">SQLite in Production with WAL</title><link href="https://victoria.dev/posts/sqlite-in-production-with-wal/"/><id>https://victoria.dev/posts/sqlite-in-production-with-wal/</id><author><name>Victoria Drake</name></author><published>2020-03-05T10:14:43-05:00</published><updated>2020-03-05T10:14:43-05:00</updated><content type="html"><![CDATA[<p><em>Update: read the <a href="https://news.ycombinator.com/item?id=27237919">HackerNews discussion</a>.</em></p>
<p>So you need a database. It&rsquo;s going to handle a few hundred users and mostly read operations. Time to set up a PostgreSQL cluster, debate connection pooling strategies, configure replication, and design backup procedures&hellip; right?</p>
<p>When I say, &ldquo;What about SQLite?&rdquo;, the response is usually some variation of &ldquo;That&rsquo;s not a real database.&rdquo;</p>
<p>This reaction reveals something important about how engineering teams make technology decisions. We often choose tools based on what sounds impressive rather than what solves our actual problems. SQLite represents an underappreciated truth in engineering leadership: sometimes the boring, simple solution is exactly what your team needs.</p>
<p><del><a href="https://sqlite.org/index.html">SQLite</a></del> (&ldquo;see-quell-lite&rdquo;) is a lightweight SQL database engine that&rsquo;s self-contained in a single file. It&rsquo;s library, database, and data, all in one package. For certain applications, SQLite is a solid choice for a production database. It&rsquo;s lightweight, ultra-portable, and has no external dependencies.</p>
<h2 id="matching-tools-to-actual-requirements">Matching Tools to Actual Requirements</h2>
<p>As an engineering leader, one of your most important responsibilities is helping your team choose appropriate technology rather than impressive technology. SQLite excels in specific scenarios that are more common than most teams realize.</p>
<p>SQLite is best suited for production use in applications that:</p>
<ul>
<li>Desire fast and simple setup</li>
<li>Require high reliability in a small package</li>
<li>Have, and want to retain, a small footprint</li>
<li>Are read-heavy but not write-heavy</li>
<li>Don&rsquo;t need multiple user accounts or features like multiversion concurrency snapshots</li>
</ul>
<p>These criteria describe a significant percentage of web applications, internal tools, and even customer-facing products. But teams often dismiss SQLite because it doesn&rsquo;t match their mental model of what a &ldquo;serious&rdquo; database looks like.</p>
<p>Recognizing when your team&rsquo;s technology choices are driven by resume-driven development rather than problem-solving can save you oodles of time and budget wiggle room. Complex solutions carry hidden costs in deployment complexity, operational overhead, and cognitive load that simple solutions avoid entirely.</p>
<h2 id="understanding-the-technical-trade-offs">Understanding the Technical Trade-offs</h2>
<p>To guide these decisions effectively, it helps to understand the technical details well enough to evaluate trade-offs intelligently. In the case of SQLite, you can examine its performance characteristics to make this evaluation concrete:</p>
<h3 id="database-transaction-modes">Database Transaction Modes</h3>
<p>POSIX system call fsync() commits buffered data (data saved in the operating system cache) referred to by a specified file descriptor to permanent storage or disk. This is relevant to understanding the difference between SQLite&rsquo;s two modes, as fsync() will block until the device reports the transfer is complete.</p>
<p>SQLite uses <del><a href="https://sqlite.org/atomiccommit.html">atomic commits</a></del> to batch database changes into single transactions, enabling apparent simultaneous writing of multiple operations. This is accomplished through one of two modes: rollback journal or write-ahead log (WAL).</p>
<h3 id="rollback-journal-mode">Rollback Journal Mode</h3>
<p>A <a href="https://www.sqlite.org/lockingv3.html#rollback">rollback journal</a> is essentially a back-up file created by SQLite before write changes occur on a database file. It has the advantage of providing high reliability by helping SQLite restore the database to its original state in case a write operation is compromised during the disk-writing process.</p>
<p>Assuming a cold cache, SQLite first needs to read the relevant pages from a database file before it can write to it. Information is read out into the operating system cache, then transferred into user space. SQLite obtains a reserved lock on the database file, preventing other processes from writing to the database. At this point, other processes may still read from the database.</p>
<p>SQLite creates a separate file, the rollback journal, with the original content of the pages that will be changed. Initially existing in the cache, the rollback journal is written to persistent disk storage with <code>fsync()</code> to enable SQLite to restore the database should its next operations be compromised.</p>
<p>SQLite then obtains an exclusive lock preventing other processes from reading or writing, and writes the page changes to the database file in cache. Since writing to disk is slower than interaction with the cache, writing to disk doesn&rsquo;t occur immediately. The rollback journal continues to exist until changes are safely written to disk with a second <code>fsync()</code>. From a user-space process point of view, the change to the disk (the COMMIT, or end of the transaction) happens instantaneously once the rollback journal is deleted - hence, atomic commits. However, the two <code>fsync()</code> operations required to complete the COMMIT make this option, from a transactional standpoint, slower than SQLite&rsquo;s lesser known WAL mode.</p>
<h3 id="write-ahead-logging-wal">Write-ahead logging (WAL)</h3>
<p>While the rollback journal method uses a separate file to preserve the original database state, the <a href="https://www.sqlite.org/wal.html">WAL method</a> uses a separate WAL file to instead record the changes. Instead of a COMMIT depending on writing changes to disk, a COMMIT in WAL mode occurs when a record of one or more commits is appended to the WAL. This has the advantage of not requiring blocking read or write operations to the database file in order to make a COMMIT, so more transactions can happen concurrently.</p>
<p>WAL mode introduces the concept of the checkpoint, which is when the WAL file is synced to persistent storage before all its transactions are transferred to the database file. You can optionally specify when this occurs, but SQLite provides reasonable defaults. The checkpoint is the WAL version of the atomic commit.</p>
<p>In WAL mode, write transactions are performed faster than in the traditional rollback journal mode. Each transaction involves writing the changes only once to the WAL file instead of twice - to the rollback journal, and then to disk - before the COMMIT signals that the transaction is over.</p>
<p>For teams handling moderate write loads, WAL mode often provides the performance characteristics they actually need without the operational complexity of distributed databases.</p>
<h2 id="the-performance-reality">The Performance Reality</h2>
<p>Benchmarks tell a compelling story about SQLite&rsquo;s practical capabilities. On modest hardware—the smallest EC2 instance with no provisioned IOPS—SQLite with WAL mode handles 400 write transactions per second and thousands of reads. For many applications, this represents more capacity than they need.</p>
<p>These numbers matter because they provide concrete data for technology discussions. Instead of theoretical conversations about &ldquo;what if we need to scale,&rdquo; you can evaluate whether 400 writes per second actually meets your requirements. Often, it does—with significant room for growth.</p>
<p>More importantly, SQLite eliminates entire categories of operational complexity: connection pooling, database server maintenance, backup procedures, replication configuration, and deployment coordination. The operational overhead you don&rsquo;t have to manage often provides more value than the theoretical scalability you might need someday.</p>
<h2 id="making-strategic-technology-decisions">Making Strategic Technology Decisions</h2>
<p>Engineering teams often equate complexity with sophistication and assume that simple solutions won&rsquo;t scale or aren&rsquo;t &ldquo;enterprise-ready&rdquo; without considering the actual requirements of the enterprise. The SQLite decision exemplifies a broader principle in engineering leadership: optimizing for actual constraints rather than imaginary ones. This requires understanding both the technical capabilities of your options and the real requirements of your systems.</p>
<p>This means asking your teams to articulate specific performance requirements, operational constraints, and growth projections rather than making technology choices based on industry trends or resume building. It means evaluating the total cost of ownership including deployment complexity, operational overhead, and team cognitive load.</p>
<p>Most importantly, it means recognizing that the best technology choice is often the one that solves your current problems effectively while remaining simple enough to understand, maintain, and evolve as requirements change.</p>
<h2 id="building-a-culture-of-appropriate-technology">Building a Culture of Appropriate Technology</h2>
<p>Teams that consistently make good technology choices develop systematic approaches to evaluation rather than relying on instinct or industry hype. They start with requirements, evaluate options based on total cost of ownership, and choose solutions that match their actual needs rather than their aspirational ones.</p>
<p>This culture emerges when leaders model technical decision-making that prioritizes problem-solving over impressiveness. When you advocate for SQLite over PostgreSQL because it better matches your workload, you&rsquo;re teaching your team to think critically about technology trade-offs.</p>
<p>The long-term impact is teams that build sustainable systems they can actually maintain and evolve. Simple solutions that solve real problems create more value than complex solutions that solve theoretical ones.</p>
<p>For medium-sized, read-heavy applications, SQLite with WAL mode represents exactly this kind of appropriate technology choice. It provides perfectly adequate capability in a perfectly compact package—which is often exactly what your application needs.</p>
]]></content></entry><entry><title type="html">17 Minutes to 16 Seconds: a 60x Performance Improvement from… Python?!</title><link href="https://victoria.dev/posts/17-minutes-to-16-seconds-a-60x-performance-improvement-from-python/"/><id>https://victoria.dev/posts/17-minutes-to-16-seconds-a-60x-performance-improvement-from-python/</id><author><name>Victoria Drake</name></author><published>2020-02-28T09:31:02-05:00</published><updated>2020-02-28T09:31:02-05:00</updated><content type="html"><![CDATA[<p>Engineering teams will spend weeks optimizing database queries that run in milliseconds while ignoring network requests that take hundreds of milliseconds. They’ll debate the performance implications of different sorting algorithms while their application spends seventeen minutes on network latency to process a few hundred requests. This misallocation of optimization effort reveals a common leadership challenge: helping teams identify and focus on the bottlenecks that actually matter.</p>
<p>When I developed <a href="https://github.com/victoriadrake/hydra-link-checker">Hydra</a>, a multithreaded link checker written in Python, the performance requirements were clear. It needed to run as part of CI/CD processes, which meant speed was essential for developer productivity. Nobody wants to wait seventeen minutes to learn whether their build succeeded—that’s long enough to make coffee, check Twitter, question your career choices, and wonder if the process crashed.</p>
<p>The project became a case study in systematic performance optimization and the leadership decisions that guide technical implementation. Unlike many Python site crawlers that rely on external dependencies like BeautifulSoup, Hydra uses only standard libraries. This constraint required thinking carefully about how to achieve optimal performance within Python’s limitations.</p>
<h2 id="understanding-the-performance-landscape">Understanding the Performance Landscape</h2>
<p>As an engineering leader, one of your most important responsibilities is helping your team understand where performance problems actually occur versus where they assume they occur. Most developers have an intuitive sense that network operations are slower than CPU operations, but the actual magnitude of these differences is staggering.</p>
<p>Here are approximate timings for tasks performed on a typical PC:</p>
<table>
<thead>
<tr>
<th></th>
<th>Task</th>
<th>Time</th>
</tr>
</thead>
<tbody>
<tr>
<td>CPU</td>
<td>execute typical instruction</td>
<td>1/1,000,000,000 sec = 1 nanosec</td>
</tr>
<tr>
<td>CPU</td>
<td>fetch from L1 cache memory</td>
<td>0.5 nanosec</td>
</tr>
<tr>
<td>CPU</td>
<td>branch misprediction</td>
<td>5 nanosec</td>
</tr>
<tr>
<td>CPU</td>
<td>fetch from L2 cache memory</td>
<td>7 nanosec</td>
</tr>
<tr>
<td>RAM</td>
<td>Mutex lock/unlock</td>
<td>25 nanosec</td>
</tr>
<tr>
<td>RAM</td>
<td>fetch from main memory</td>
<td>100 nanosec</td>
</tr>
<tr>
<td>Network</td>
<td>send 2K bytes over 1Gbps network</td>
<td>20,000 nanosec</td>
</tr>
<tr>
<td>RAM</td>
<td>read 1MB sequentially from memory</td>
<td>250,000 nanosec</td>
</tr>
<tr>
<td>Disk</td>
<td>fetch from new disk location (seek)</td>
<td>8,000,000 nanosec   (8ms)</td>
</tr>
<tr>
<td>Disk</td>
<td>read 1MB sequentially from disk</td>
<td>20,000,000 nanosec  (20ms)</td>
</tr>
<tr>
<td>Network</td>
<td>send packet US to Europe and back</td>
<td>150,000,000 nanosec (150ms)</td>
</tr>
</tbody>
</table>
<p>Peter Norvig first published these numbers in <a href="http://norvig.com/21-days.html#answers">Teach Yourself Programming in Ten Years</a>. While hardware continues to evolve, the relative relationships remain humbling for anyone who’s ever spent time optimizing the wrong thing.</p>
<p>Notice that sending a simple packet over the Internet is over a million times slower than fetching from RAM. These aren’t small performance differences—they’re fundamental constraints that should guide every optimization decision you make.</p>
<p>For Hydra, parsing response data and assembling results happens on the CPU and is relatively fast. The overwhelming bottleneck—by over six orders of magnitude—is network latency. Any optimization effort that didn’t address network I/O would miss the point.</p>
<h2 id="working-within-pythons-constraints">Working Within Python’s Constraints</h2>
<p>Python presents an interesting challenge for performance-critical applications. The Global Interpreter Lock (GIL) prevents multiple threads from executing Python bytecodes simultaneously—each thread must wait for the GIL to be released by the currently executing thread. This eliminates race conditions but also prevents true parallel execution of CPU-bound tasks.</p>
<p>For many engineering teams, this limitation becomes a reason to dismiss Python entirely for performance work. But effective technical leadership involves understanding how to work within constraints rather than avoiding tools with limitations.</p>
<p>The key insight is that Python’s GIL limitation doesn’t apply uniformly. While CPU-bound tasks suffer from the GIL, I/O-bound tasks can benefit from concurrent execution because the GIL is released during I/O operations. For Hydra’s use case—fetching web pages over the network—multithreading in Python can provide significant performance improvements despite the GIL.</p>
<p>This distinction matters for strategic technical decisions. Instead of automatically reaching for Go or Rust when performance requirements emerge, understanding Python’s actual constraints can enable better technology choices based on specific workload characteristics.</p>
<h2 id="choosing-the-right-concurrency-model">Choosing the Right Concurrency Model</h2>
<p>Python provides multiple approaches to parallel execution, each suited for different types of bottlenecks. Making the right choice requires understanding both technical trade-offs and your application’s specific performance characteristics.</p>
<h3 id="multiple-processes">Multiple Processes</h3>
<p>Python’s <a href="https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.ProcessPoolExecutor"><code>ProcessPoolExecutor</code></a> uses worker subprocesses to bypass the GIL entirely. This approach maximizes parallelization for CPU-bound tasks by utilizing multiple processor cores effectively.</p>
<p>For compute-heavy operations—mathematical calculations, data processing, algorithm execution—multiple processes provide genuine parallel execution. However, this carries overhead costs in memory usage and inter-process communication that may not be justified for I/O-bound workloads.</p>
<h3 id="multiple-threads">Multiple Threads</h3>
<p>Python’s <a href="https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.ThreadPoolExecutor"><code>ThreadPoolExecutor</code></a> uses a pool of threads that can execute I/O operations concurrently. While threads can’t execute Python code in parallel due to the GIL, they can perform I/O operations concurrently because the GIL is released during system calls.</p>
<p>For I/O-bound applications—web scraping, API calls, file operations—threading provides excellent performance improvements with lower overhead than multiprocessing.</p>
<h2 id="implementation-strategy">Implementation Strategy</h2>
<p>Here’s how Hydra uses <code>ThreadPoolExecutor</code> to achieve concurrent link checking:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py" data-lang="py"><span style="display:flex;"><span><span style="color:#75715e"># Create the Checker class</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Checker</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Queue of links to be checked</span>
</span></span><span style="display:flex;"><span>    TO_PROCESS <span style="color:#f92672">=</span> Queue()
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Maximum workers to run</span>
</span></span><span style="display:flex;"><span>    THREADS <span style="color:#f92672">=</span> <span style="color:#ae81ff">100</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Maximum seconds to wait for HTTP response</span>
</span></span><span style="display:flex;"><span>    TIMEOUT <span style="color:#f92672">=</span> <span style="color:#ae81ff">60</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> __init__(self, url):
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># Create the thread pool</span>
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>pool <span style="color:#f92672">=</span> futures<span style="color:#f92672">.</span>ThreadPoolExecutor(max_workers<span style="color:#f92672">=</span>self<span style="color:#f92672">.</span>THREADS)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">run</span>(self):
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Run until the TO_PROCESS queue is empty</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">while</span> <span style="color:#66d9ef">True</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">try</span>:
</span></span><span style="display:flex;"><span>            target_url <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>TO_PROCESS<span style="color:#f92672">.</span>get(block<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>, timeout<span style="color:#f92672">=</span><span style="color:#ae81ff">2</span>)
</span></span><span style="display:flex;"><span>            <span style="color:#75715e"># If we haven&#39;t already checked this link</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> target_url[<span style="color:#e6db74">&#34;url&#34;</span>] <span style="color:#f92672">not</span> <span style="color:#f92672">in</span> self<span style="color:#f92672">.</span>visited:
</span></span><span style="display:flex;"><span>                <span style="color:#75715e"># Mark it as visited</span>
</span></span><span style="display:flex;"><span>                self<span style="color:#f92672">.</span>visited<span style="color:#f92672">.</span>add(target_url[<span style="color:#e6db74">&#34;url&#34;</span>])
</span></span><span style="display:flex;"><span>                <span style="color:#75715e"># Submit the link to the pool</span>
</span></span><span style="display:flex;"><span>                job <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>pool<span style="color:#f92672">.</span>submit(self<span style="color:#f92672">.</span>load_url, target_url, self<span style="color:#f92672">.</span>TIMEOUT)
</span></span><span style="display:flex;"><span>                job<span style="color:#f92672">.</span>add_done_callback(self<span style="color:#f92672">.</span>handle_future)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">except</span> Empty:
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">except</span> <span style="color:#a6e22e">Exception</span> <span style="color:#66d9ef">as</span> e:
</span></span><span style="display:flex;"><span>            print(e)
</span></span></code></pre></div><p>The implementation reflects several engineering leadership principles. The thread pool size (100 workers) was determined through profiling and testing rather than guesswork. The timeout mechanism prevents slow requests from blocking overall progress. The callback pattern enables efficient result processing without blocking the main execution thread.</p>
<h2 id="measuring-real-impact">Measuring Real Impact</h2>
<p>Performance optimization discussions often remain theoretical without concrete measurements. For Hydra, the improvement was dramatic. Here’s a comparison between the run times for checking my website with a prototype single-thread program and using Hydra:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>time python3 slow-link-check.py https://victoria.dev
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>real    17m34.084s
</span></span><span style="display:flex;"><span>user    11m40.761s
</span></span><span style="display:flex;"><span>sys     0m5.436s
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>time python3 hydra.py https://victoria.dev
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>real    0m15.729s
</span></span><span style="display:flex;"><span>user    0m11.071s
</span></span><span style="display:flex;"><span>sys     0m2.526s
</span></span></code></pre></div><p>The single-threaded implementation took over seventeen minutes. The multithreaded version completed in under sixteen seconds. That’s a performance improvement of more than 60x.</p>
<p>These aren’t marginal gains from micro-optimizations. They represent fundamental improvements in application efficiency that users immediately notice. While specific timings vary based on site size and network conditions, the order-of-magnitude improvement demonstrates the value of addressing actual bottlenecks systematically.</p>
<h2 id="leadership-lessons-in-performance-optimization">Leadership Lessons in Performance Optimization</h2>
<p>The Hydra project illustrates several principles that engineering leaders can apply across different technologies and applications.</p>
<p><strong>Focus on actual bottlenecks, not theoretical ones.</strong> Teams often optimize the wrong things because they focus on code that feels slow to write rather than code that’s actually slow to execute. Teaching teams to measure and identify real performance constraints prevents wasted optimization effort.</p>
<p><strong>Understand your tools’ limitations and strengths.</strong> Python’s GIL is a constraint, but it doesn’t preclude high-performance applications in the right contexts. Effective technical leadership involves understanding how to work within technological constraints rather than avoiding tools with limitations.</p>
<p><strong>Make optimization decisions based on requirements.</strong> Hydra needed to run quickly in CI/CD environments, which justified the development effort for custom multithreading. But this level of effort isn’t required in every Python application. Understanding your specific requirements helps allocate development efforts appropriately.</p>
<p><strong>Measure improvement, don’t assume it.</strong> Performance optimization can introduce complexity and maintenance overhead. Concrete measurements ensure that optimization efforts provide sufficient value to justify their costs.</p>
<h2 id="building-performance-conscious-teams">Building Performance-Conscious Teams</h2>
<p>The most effective engineering teams develop systematic approaches to performance rather than relying on intuition or premature optimization. This requires creating culture and processes that encourage measurement, analysis, and strategic optimization decisions.</p>
<p>This means teaching teams to profile applications before optimizing them, helping them understand the performance characteristics of their technology choices, and ensuring that optimization efforts align with actual user requirements rather than theoretical concerns.</p>
<p>Most importantly, it means recognizing that performance optimization is a technical leadership skill that involves strategic thinking about trade-offs, constraints, and business requirements—not just implementation knowledge.</p>
<h2 id="the-real-lesson">The Real Lesson</h2>
<p>Hydra’s performance gains from 17 minutes to 16 seconds teaches a lesson that applies far beyond Python: measure first, optimize second, and always focus on the constraint that’s actually limiting your system. Whether you’re debugging performance bottlenecks or organizational inefficiencies, the biggest wins come from addressing the right problem rather than optimizing the wrong one exceptionally well.</p>
<p>The next time your team debates whether to rewrite everything in Go for performance, remember Hydra&rsquo;s 60x improvement using standard Python libraries. Sometimes the most effective optimization is the one you can implement this week rather than the solution you&rsquo;ll build next quarter… or the quarter after that.</p>
]]></content></entry><entry><title type="html">From 17 Minutes to 8 Seconds: Strategic Performance Optimization for Engineering Teams</title><link href="https://victoria.dev/posts/from-17-minutes-to-8-seconds-strategic-performance-optimization-for-engineering-teams/"/><id>https://victoria.dev/posts/from-17-minutes-to-8-seconds-strategic-performance-optimization-for-engineering-teams/</id><author><name>Victoria Drake</name></author><published>2020-02-25T12:50:29-05:00</published><updated>2020-02-25T12:50:29-05:00</updated><content type="html"><![CDATA[<p>Leading engineering teams means constantly balancing technical excellence with organizational needs. I found myself facing a perfect example of this challenge when helping out the Open Web Application Security Project (OWASP). When I joined the core team for OWASP&rsquo;s Web Security Testing Guide, I found a critical infrastructure problem that was silently undermining both our security mission and our ability to ship quality work efficiently.</p>
<p>OWASP is a big organization with an even bigger website to match. The site serves hundreds of thousands of visitors with cybersecurity resources that security professionals worldwide depend on. But beneath this successful exterior, we had a problem that most engineering leaders will recognize: broken processes that no one had time to fix, creating cascading inefficiencies across our entire development workflow.</p>
<p>OWASP.org lacked any centralized quality assurance processes and was riddled with broken links. Customers don’t like broken links; attackers really do. These weren&rsquo;t just user experience issues—they represented real security vulnerabilities that could enable attacks like broken link hijacking and subdomain takeovers. Here we were, an organization dedicated to web security, with our own infrastructure exposing the exact vulnerabilities we taught others to prevent.</p>
<h2 id="when-infrastructure-problems-become-leadership-problems">When Infrastructure Problems Become Leadership Problems</h2>
<p>The broken link problem at OWASP had all the hallmarks of technical debt that had become organizational debt: volunteers avoided updating content because they knew links might break, and quality suffered because manual checking was impractical. Our CI/CD pipeline had a glaring gap where automated link validation should have been.</p>
<p>The underlying issue was both technical and strategic. We needed a solution that could integrate into our development workflow, scale with our volunteer contributor model, and actually get adopted by teams who were already stretched thin. This meant thinking beyond just building a tool; I needed to design a solution that addressed the human and process challenges alongside the technical ones.</p>
<h2 id="strategic-requirements-beyond-just-make-it-work">Strategic Requirements Beyond Just &ldquo;Make It Work&rdquo;</h2>
<p>When I proposed building an automated link checking solution, the requirements went far beyond technical functionality. As engineering leaders, we know that tools succeed or fail based on adoption, maintainability, and organizational fit. Our solution needed to:</p>
<ul>
<li>Integrate seamlessly into existing CI/CD workflows without disrupting volunteer contributors</li>
<li>Provide actionable reports that non-technical content maintainers could understand and act on</li>
<li>Run efficiently enough to avoid becoming a bottleneck in our deployment process</li>
<li>Scale with OWASP&rsquo;s distributed, volunteer-driven development model</li>
</ul>
<p>The technical challenge was, essentially, to build a web crawler. The leadership challenge was ensuring it would actually solve our organizational problem rather than just creating another tool that sits unused.</p>
<p>This required making strategic decisions about language choice, architecture, and performance that balanced multiple constraints: team familiarity (Python was the common denominator), performance requirements (CI/CD integration demanded speed), and long-term maintainability (volunteers needed to be able to contribute to the codebase).</p>
<h2 id="understanding-the-real-cost-of-performance-bottlenecks">Understanding the Real Cost of Performance Bottlenecks</h2>
<p>As engineering leaders, we need to think about performance in terms of organizational impact, not just technical metrics. The latency numbers that every developer should know tell a story about where bottlenecks hide and how they compound:</p>
<table>
<thead>
<tr>
<th>Type</th>
<th>Task</th>
<th>Time</th>
</tr>
</thead>
<tbody>
<tr>
<td>CPU</td>
<td>execute typical instruction</td>
<td>1/1,000,000,000 sec = 1 nanosec</td>
</tr>
<tr>
<td>CPU</td>
<td>fetch from L1 cache memory</td>
<td>0.5 nanosec</td>
</tr>
<tr>
<td>CPU</td>
<td>branch misprediction</td>
<td>5 nanosec</td>
</tr>
<tr>
<td>CPU</td>
<td>fetch from L2 cache memory</td>
<td>7 nanosec</td>
</tr>
<tr>
<td>RAM</td>
<td>Mutex lock/unlock</td>
<td>25 nanosec</td>
</tr>
<tr>
<td>RAM</td>
<td>fetch from main memory</td>
<td>100 nanosec</td>
</tr>
<tr>
<td>RAM</td>
<td>read 1MB sequentially from memory</td>
<td>250,000 nanosec</td>
</tr>
<tr>
<td>Disk</td>
<td>fetch from new disk location (seek)</td>
<td>8,000,000 nanosec   (8ms)</td>
</tr>
<tr>
<td>Disk</td>
<td>read 1MB sequentially from disk</td>
<td>20,000,000 nanosec  (20ms)</td>
</tr>
<tr>
<td>Network</td>
<td>send packet US to Europe and back</td>
<td>150,000,000 nanosec (150ms)</td>
</tr>
</tbody>
</table>
<p>Peter Norvig first published these numbers some years ago in <a href="http://norvig.com/21-days.html#answers">Teach Yourself Programming in Ten Years</a>. While technology changes over the decades, the order-of-magnitude differences between these numbers remain as devastatingly accurate as ever.</p>
<p>These numbers reveal something critical for engineering leaders to know: network operations are over a million times slower than memory operations. In our link checker, every HTTP request was a network operation, meaning we were dealing with the slowest possible operation for a process that needed to run fast and efficiently in CI/CD.</p>
<p>A single-thread crawler workflow creates an inherent bottleneck:</p>
<ol>
<li>Fetch HTML from a page (network-bound operation)</li>
<li>Parse links from the HTML content</li>
<li>Validate each link by making HTTP requests (more network-bound operations)</li>
<li>Track visited links to avoid duplicate work</li>
<li>Repeat for every page found</li>
</ol>
<figure class="screenshot"><img src="/posts/from-17-minutes-to-8-seconds-strategic-performance-optimization-for-engineering-teams/execution_flow.png"
    alt="A flow chart showing program execution">
</figure>

<p>Mapping out the execution flow makes the issue clear to see: this process was fundamentally serial, with network latency dominating every step. For a site like OWASP.org with over 12,000 links, this meant potential runtime measured in hours, not minutes.</p>
<p>Bottlenecks like this cascade through entire organizations, affecting developer productivity, deployment confidence, and ultimately in the case of OWASP, our ability to deliver on our security mission effectively.</p>
<p>Checking these links serially would guarantee a performance bottleneck that would hurt team productivity, deployment confidence, and our ability to ship quality software consistently.</p>
<h2 id="how-bottlenecks-cascade-through-engineering-organizations">How Bottlenecks Cascade Through Engineering Organizations</h2>
<p>How long would it have taken to check all 12,000 links on OWASP.org with a single-thread web crawler? We can make a rough estimate:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>      150 milliseconds per network request
</span></span><span style="display:flex;"><span> x 12,000 links on OWASP.org
</span></span><span style="display:flex;"><span>---------
</span></span><span style="display:flex;"><span>1,800,000 milliseconds (30 minutes minimum)
</span></span></code></pre></div><p>A whole half hour, just for the network tasks. In the real world it would likely be much slower than that, since web pages are frequently much larger than one packet.</p>
<p>When your CI/CD pipeline includes a (very conservative minimum) 30-minute bottleneck, the impact extends far beyond technical metrics. Several things happen:</p>
<p>First, your feedback loops become painfully long. Contributors push changes and then wait more than half an hour to learn if they&rsquo;ve broken anything. This delays iteration, reduces deployment confidence, and ultimately makes your team more conservative about shipping improvements.</p>
<p>Second, to add insult to injury, the financial impact compounds quickly. In serverless environments like AWS Lambda, compute time directly translates to cost. A process that takes 30 minutes instead of seconds doesn&rsquo;t just waste time—it multiplies your infrastructure costs dramatically.</p>
<figure><img src="/posts/from-17-minutes-to-8-seconds-strategic-performance-optimization-for-engineering-teams/lambda-chart.png"
    alt="Chart showing Total Lambda compute cost by function execution"><figcaption>
      <p>Source: <a href="https://serverless.com/blog/understanding-and-controlling-aws-lambda-costs/">Understanding and Controlling AWS Lambda Costs</a></p>
    </figcaption>
</figure>

<p>But the hidden cost is team productivity. When your deployment pipeline has unpredictable bottlenecks, teams start working around them. They try to batch changes into huge PRs instead of making small incremental (and easier to merge) improvements. They skip running full test suites locally. They become hesitant to refactor or make structural improvements that might require multiple deployment cycles to validate.</p>
<p>Identifying and resolving bottlenecks can make the difference between teams that stall at fixing bugs and teams that ship new features fast.</p>
<h2 id="making-strategic-technology-decisions-under-constraints">Making Strategic Technology Decisions Under Constraints</h2>
<p>This is where engineering leadership gets interesting: balancing competing constraints while making decisions that your team can actually execute on. I had to choose between Python (a comfortable language choice for everyone in the OWASP group) and Go (which offered better concurrency primitives and performance characteristics).</p>
<p>The decision matrix looked like this:</p>
<ul>
<li><strong><strong>Team familiarity</strong></strong>: Python had broad adoption across OWASP contributors</li>
<li><strong><strong>Performance requirements</strong></strong>: Go&rsquo;s goroutines made concurrent programming more straightforward</li>
<li><strong><strong>Maintainability</strong></strong>: We needed something contributors could debug and extend</li>
<li><strong><strong>Long-term scalability</strong></strong>: The solution needed to handle growing content without constant optimization</li>
</ul>
<p>I chose to prototype the link checker in both languages. I built a multithreaded Python version that I dubbed <a href="https://github.com/victoriadrake/hydra-link-checker">Hydra</a>, and a Go version that took advantage of goroutines. This gave us concrete data to inform the decision rather than relying on assumptions. This approach—building multiple solutions to validate architectural choices—is something I&rsquo;ve found invaluable for critical infrastructure decisions.</p>
<h2 id="designing-solutions-that-scale-with-your-team">Designing Solutions That Scale With Your Team</h2>
<p>The good news is that once you identify a bottleneck, you can resolve it. Whether it&rsquo;s scaling work efficiently across your team, code reviews, incident response, or in our case, link validation, the principle is the same: address the slowest operation.</p>
<p>Think of our single-thread web crawler as if it were one person handling all the work sequentially. The work gets done, but one person doesn&rsquo;t scale well to thousands of requests. Working in serial, each request has to wait for the previous one to complete, creating an artificial constraint where we&rsquo;re limited by the slowest individual operation.</p>
<p>Thankfully, link validation is an embarrassingly parallel problem. Each link can be checked independently, which means we could distribute the work across multiple concurrent processes, like having several people split up the work to help it go faster. In computing this is called multithreading.</p>
<p>By designing for concurrency from the start and building a multithreaded link checker, we&rsquo;d have solution that could scale with different deployment environments, handle varying load patterns, and remain responsive even as OWASP&rsquo;s content grew.</p>
<p>To illustrate, here are some snippets from the Go implementation. They incorporate some architectural insights that are relevant for any engineering leader designing concurrent systems.</p>
<h3 id="1-safe-concurrent-access">1. Safe Concurrent Access</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Checker</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">startDomain</span>             <span style="color:#f92672">**</span><span style="color:#66d9ef">string</span><span style="color:#f92672">**</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">brokenLinks</span>             []<span style="color:#a6e22e">Result</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">visitedLinks</span>            <span style="color:#66d9ef">map</span>[<span style="color:#f92672">**</span><span style="color:#66d9ef">string</span><span style="color:#f92672">**</span>]<span style="color:#f92672">**</span><span style="color:#66d9ef">bool</span><span style="color:#f92672">**</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">workerCount</span>, <span style="color:#a6e22e">maxWorkers</span> <span style="color:#f92672">**</span><span style="color:#66d9ef">int</span><span style="color:#f92672">**</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">Mutex</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The <code>sync.Mutex</code> ensures our shared state remains consistent across goroutines, while the <code>visitedLinks</code> map uses O(1) lookup time to avoid creating new bottlenecks as our dataset grows.</p>
<blockquote>
<p>When optimizing one constraint like network latency, make sure you&rsquo;re not inadvertently creating new constraints elsewhere—like O(n) lookup times that degrade performance as your data grows.</p>
</blockquote>
<h3 id="2-throttling">2. Throttling</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">toProcess</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Add</span>(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">checker</span>.<span style="color:#a6e22e">addWorker</span>()
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">go</span> <span style="color:#a6e22e">worker</span>(<span style="color:#a6e22e">i</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">checker</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">wg</span>, <span style="color:#a6e22e">toProcess</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">checker</span>.<span style="color:#a6e22e">workerCount</span> &gt; <span style="color:#a6e22e">checker</span>.<span style="color:#a6e22e">maxWorkers</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Sleep</span>(<span style="color:#ae81ff">1</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>) <span style="color:#f92672">*</span><span style="color:#75715e">// throttle down*
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Wait</span>()
</span></span></code></pre></div><p>Even when you can parallelize work, you need to respect system boundaries. Too many concurrent HTTP requests could overwhelm target servers or trigger rate limiting, so we built in backpressure to ensure our optimization doesn&rsquo;t create problems for others. This is an effective way to balance between performance and being a good network citizen.</p>
<h2 id="measuring-impact-the-results-that-matter-for-engineering-teams">Measuring Impact: The Results That Matter for Engineering Teams</h2>
<p>To obtain some concrete data, I compared the numbers between three implementations: a prototype single-thread Python program, the multithreaded Hydra version, and an implementation written in Go. The performance data from our three implementations tells a story about strategic technology choices and their organizational impact. Here&rsquo;s a comparison run against my website with its few hundred links:</p>
<h3 id="single-threaded-python-prototype">Single-Threaded Python Prototype</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>time python3 slow-link-check.py https://victoria.dev
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>real 17m34.084s
</span></span><span style="display:flex;"><span>user 11m40.761s
</span></span><span style="display:flex;"><span>sys     0m5.436s
</span></span></code></pre></div><p>Seventeen minutes for a site much smaller than OWASP.org meant our original approach would have been completely unusable in a CI/CD context.</p>
<h3 id="hydra-multithreaded-python-version">Hydra: Multithreaded Python Version</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>time python3 hydra.py https://victoria.dev
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>real 1m13.358s
</span></span><span style="display:flex;"><span>user 0m13.161s
</span></span><span style="display:flex;"><span>sys     0m2.826s
</span></span></code></pre></div><p>The concurrency improvements brought us down to just over a minute—a 15x improvement that made CI/CD integration viable.</p>
<h3 id="go-implementation">Go Implementation</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>time ./go-link-check --url=https://victoria.dev
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>real 0m7.926s
</span></span><span style="display:flex;"><span>user 0m9.044s
</span></span><span style="display:flex;"><span>sys     0m0.932s
</span></span></code></pre></div><p>Eight seconds. This performance improvement fundamentally changed how teams could interact with the tool. With this level of efficiency, link checking could become part of every deployment without friction. Contributors wouldn&rsquo;t think twice about running it locally. Instead of being a barrier, link checking would be invisible infrastructure.</p>
<p>As fun as it is to simply enjoy the speedups, we can directly relate these results to everything we’ve discussed so far. Consider taking a process that used to soak up seventeen minutes and turning it into an eight-second-affair instead. Not only will that give developers a much shorter and more efficient feedback loop, it gives teams the ability to develop faster while costing less. To drive the point home: a process that runs in seventeen-and-a-half minutes instead of eight seconds will also cost over a hundred and thirty times more to run.</p>
<p>These numbers represent more than technical metrics. They show how strategic performance optimization can transform a tool from something teams avoid to something they rely on.</p>
<h2 id="the-leadership-framework-turning-performance-wins-into-organizational-impact">The Leadership Framework: Turning Performance Wins Into Organizational Impact</h2>
<p>The 130x performance improvement we achieved demonstrates a leadership approach to identifying and breaking bottlenecks that affects entire engineering organizations.</p>
<p>When engineering leaders see a 17-minute process become an 8-second process, we should be asking: what other critical workflows are creating similar friction? Where else are teams working around inefficient processes instead of addressing them? How many small compounding delays are preventing our organization from shipping quality software consistently?</p>
<p>The OWASP link checker became a case study for our broader infrastructure strategy. We learned that volunteer contributors were more likely to maintain content quality when the feedback loop was immediate. We discovered that CI/CD performance directly influenced how teams approached incremental improvements versus risky big-batch changes. Most importantly, we proved that strategic performance optimization could transform organizational behavior.</p>
<p>Start with understanding the human and organizational impact, design solutions that respect team constraints, and measure success by adoption and workflow improvement. When you can turn a deployment blocker into invisible infrastructure, you&rsquo;re optimizing both code and organizational dynamics by removing friction that allows your entire team to focus on delivering value rather than fighting with tools.</p>
]]></content></entry><entry><title type="html">Command line tricks for managing your messy open source repository</title><link href="https://victoria.dev/archive/command-line-tricks-for-managing-your-messy-open-source-repository/"/><id>https://victoria.dev/archive/command-line-tricks-for-managing-your-messy-open-source-repository/</id><author><name>Victoria Drake</name></author><published>2020-02-17T08:05:06-05:00</published><updated>2020-02-17T08:05:06-05:00</updated><content type="html"><![CDATA[<p>Effective collaboration, especially in open source software development, starts with effective organization. To make sure that nothing gets missed, the general rule, &ldquo;one issue, one pull request&rdquo; is a nice rule of thumb.</p>
<p>Instead of opening an issue with a large scope like, &ldquo;Fix all the broken links in the documentation,&rdquo; open source projects will have more luck attracting contributors with several smaller and more manageable issues. In the preceding example, you might scope broken links by section or by page. This allows more contributors to jump in and dedicate small windows of their time, rather than waiting for one person to take on a larger and more tedious contribution effort.</p>
<p>Smaller scoped issues also help project maintainers see where work has been completed and where it hasn&rsquo;t. This reduces the chances that some part of the issue is missed, assumed to be completed, and later leads to bugs or security vulnerabilities.</p>
<p>That&rsquo;s all well and good; but what if you&rsquo;ve already opened several massively-scoped issues, some PRs have already been submitted or merged, and you currently have no idea where the work started or stopped?</p>
<p>It&rsquo;s going to take a little sorting out to get the state of your project back under control. Thankfully, there are a number of command line tools to help you scan, sort, and make sense of a messy repository. Here&rsquo;s a small selection of ones I use.</p>
<p>Jump to:</p>
<ul>
<li><a href="#interactive-search-and-replace-with-vim">Interactive search-and-replace with <code>vim</code></a></li>
<li><a href="#find-dead-links-in-markdown-files-with-a-node-module">Find dead links in Markdown files with a node module</a></li>
<li><a href="#list-subdirectories-with-or-without-a-git-repository-with-find">List subdirectories with or without a git repository with <code>find</code></a></li>
<li><a href="#pull-multiple-git-repositories-from-a-list-with-xargs">Pull multiple git repositories from a list with <code>xargs</code></a></li>
<li><a href="#list-issues-by-number-with-jot">List issues by number with <code>jot</code></a></li>
<li><a href="#cli-powered-open-source-organization">CLI-powered open source organization</a></li>
</ul>
<h2 id="interactive-search-and-replace-with-vim">Interactive search-and-replace with <code>vim</code></h2>
<p>You can open a file in Vim, then interactively search and replace with:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-vim" data-lang="vim"><span style="display:flex;"><span>:%<span style="color:#a6e22e">s</span><span style="color:#e6db74">/\&lt;word\&gt;/</span><span style="color:#a6e22e">newword</span>/<span style="color:#a6e22e">gc</span>
</span></span></code></pre></div><p>The <code>%</code> indicates to look in all lines of the current file; <code>s</code> is for substitute; <code>\&lt;word\&gt;</code> matches the whole word; and the <code>g</code> for &ldquo;global&rdquo; is for every occurrence. The <code>c</code> at the end will let you view and confirm each change before it&rsquo;s made. You can run it automatically, and much faster, without <code>c</code>; however, you put yourself at risk of complicating things if you&rsquo;ve made a pattern-matching error.</p>
<h2 id="find-dead-links-in-markdown-files-with-a-node-module">Find dead links in Markdown files with a node module</h2>
<p>The <a href="https://github.com/tcort/markdown-link-check">markdown-link-check</a> node module has a great <a href="https://github.com/tcort/markdown-link-check#command-line-tool">CLI buddy</a>.</p>
<p>I use this so often I turned it into a <a href="/blog/how-to-do-twice-as-much-with-half-the-keystrokes-using-.bashrc/#bash-functions">Bash alias function</a>. To do the same, add this to your <code>.bashrc</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span><span style="color:#75715e"># Markdown link check in a folder, recursive</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">function</span> mlc <span style="color:#f92672">()</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>    find $1 -name <span style="color:#ae81ff">\*</span>.md -exec markdown-link-check -p <span style="color:#f92672">{}</span> <span style="color:#ae81ff">\;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">}</span>
</span></span></code></pre></div><p>Then run with <code>mlc &lt;filename&gt;</code>.</p>
<h2 id="list-subdirectories-with-or-without-a-git-repository-with-find">List subdirectories with or without a git repository with <code>find</code></h2>
<p>Print all subdirectories that are git repositories, or in other words, have a <code>.git</code> in them:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>find . -maxdepth <span style="color:#ae81ff">1</span> -type d -exec test -e <span style="color:#e6db74">&#39;{}/.git&#39;</span> <span style="color:#e6db74">&#39;;&#39;</span> -printf <span style="color:#e6db74">&#34;is git repo: %p\n&#34;</span>
</span></span></code></pre></div><p>To print all subdirectories that are not git repositories, negate the test with <code>!</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>find . -maxdepth <span style="color:#ae81ff">1</span> -type d -exec test <span style="color:#e6db74">&#39;!&#39;</span> -e <span style="color:#e6db74">&#39;{}/.git&#39;</span> <span style="color:#e6db74">&#39;;&#39;</span> -printf <span style="color:#e6db74">&#34;not git repo: %p\n&#34;</span>
</span></span></code></pre></div><h2 id="pull-multiple-git-repositories-from-a-list-with-xargs">Pull multiple git repositories from a list with <code>xargs</code></h2>
<p>I initially used this as part of <a href="/blog/how-to-set-up-a-fresh-ubuntu-desktop-using-only-dotfiles-and-bash-scripts/">automatically re-creating my laptop with Bash scripts</a>, but it&rsquo;s pretty handy when you&rsquo;re working with cloud instances or Dockerfiles.</p>
<p>Given a file, <code>repos.txt</code> with a repository’s SSH link on each line (and your SSH keys set up), run:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>xargs -n1 git clone &lt; repos.txt
</span></span></code></pre></div><p>If you want to pull and push many repositories, I previously wrote about <a href="/posts/how-to-write-bash-one-liners-for-cloning-and-managing-github-and-gitlab-repositories/">how to use a Bash one-liner to manage your repositories</a>.</p>
<h2 id="list-issues-by-number-with-jot">List issues by number with <code>jot</code></h2>
<p>I&rsquo;m a co-author and maintainer for the <a href="https://github.com/OWASP/wstg/">OWASP Web Security Testing Guide</a> repository where I recently took one large issue (yup, it was &ldquo;Fix all the broken links in the documentation&rdquo; - how&rsquo;d you guess?) and broke it up into several smaller, more manageable issues. A whole thirty-seven smaller, more manageable issues.</p>
<p>I wanted to enumerate all the issues that the original one became, but the idea of typing out thirty-seven issue numbers (#275 through #312) seemed awfully tedious and time-consuming. So, in natural programmer fashion, I spent the same amount of time I would have used to type out all those numbers and crafted a way to automate it instead.</p>
<p>The <code>jot</code> utility (<code>apt install athena-jot</code>) is a tiny tool that&rsquo;s a big help when you want to print out some numbers. Just tell it how many you want, and where to start and stop.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span><span style="color:#75715e"># jot [ reps [ begin [ end ] ] ]</span>
</span></span><span style="display:flex;"><span>jot <span style="color:#ae81ff">37</span> <span style="color:#ae81ff">275</span> <span style="color:#ae81ff">312</span>
</span></span></code></pre></div><p>This prints each number, inclusively, from 275 to 312 on a new line. To make these into issue number notations that GitHub and many other platforms automatically recognize and turn into links, you can pipe the output to <code>awk</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>jot <span style="color:#ae81ff">37</span> <span style="color:#ae81ff">275</span> <span style="color:#ae81ff">312</span> | awk <span style="color:#e6db74">&#39;{printf &#34;#&#34;$0&#34;, &#34;}&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#275, #276, #277, #278, #279, #280, #281, #282, #283, #284, #285, #286, #287, #288, #289, #290, #291, #292, #293, #295, #296, #297, #298, #299, #300, #301, #302, #303, #304, #305, #306, #307, #308, #309, #310, #311, #312</span>
</span></span></code></pre></div><p>You can also use <code>jot</code> to generate random or redundant data, mainly for development or testing purposes.</p>
<h2 id="cli-powered-open-source-organization">CLI-powered open source organization</h2>
<p>A well-organized open source repository is a well-maintained open source project. Save this post for handy reference, and use your newfound CLI superpowers for good! 🚀</p>
]]></content></entry><entry><title type="html">Why PixelFed won&amp;#39;t save us from Instagram</title><link href="https://victoria.dev/archive/why-pixelfed-wont-save-us-from-instagram/"/><id>https://victoria.dev/archive/why-pixelfed-wont-save-us-from-instagram/</id><author><name>Victoria Drake</name></author><published>2020-02-16T19:23:20-05:00</published><updated>2020-02-16T19:23:20-05:00</updated><content type="html"><![CDATA[<p><a href="https://pixelfed.org/">PixelFed</a> is a decentralized photo sharing network based on the <a href="https://www.w3.org/TR/activitypub/">ActivityPub</a> protocol, the same one that <a href="https://joinmastodon.org/">Mastodon</a> uses. For a lot of people divorced (or wanting to be) from Instagram over <a href="https://eandt.theiet.org/content/articles/2018/04/instagram-worst-social-network-for-youth-mental-health-report-finds/">mental health concerns</a> and issues like <a href="https://techcrunch.com/2018/05/25/facebook-google-face-first-gdpr-complaints-over-forced-consent/">forced consent to post-GDPR terms</a>, a decentralized social network like PixelFed sounds like an exciting and promising alternative.</p>
<p>Personally, I stopped using Instagram once I accepted the fact that its core premise and integral structure of social interaction was encouraging me to form habits that were harmful to my life goals. I&rsquo;m not alone - studies have shown that people are <a href="https://www.bloomberg.com/opinion/articles/2019-05-01/what-we-want-doesn-t-always-make-us-happy">happier after deleting apps like Facebook</a>. The reasons for this don&rsquo;t differ greatly from why any social network can be bad for you - they&rsquo;re just found in much greater intensity on photo sharing sites, and specifically Instagram.</p>
<p>It is still early days for PixelFed. As I write this I have no way to know what kind of network it will become, or even if it will survive at all. I do know, however, that there are many glaring and fundamental problems that a decentralized photo sharing network like PixelFed won&rsquo;t solve. To elaborate, I&rsquo;m going to discuss what makes Instagram so poisonous to health, why centralized social networks aren&rsquo;t likely to ever be healthy, and why decentralized social networks have <em>a very slim</em> chance of being better.</p>
<h2 id="why-instagram-is-bad-for-your-health">Why Instagram is bad for your health</h2>
<p>Let&rsquo;s start with the basics. Your brain responds very differently to reading text than it does to looking at images.</p>
<p>It doesn&rsquo;t take more than a quick search to find hundreds of articles and studies about how reading <a href="https://www.theguardian.com/books/2014/jan/23/can-reading-make-you-smarter">can make you smarter</a>, <a href="https://bigthink.com/21st-century-spirituality/reading-rewires-your-brain-for-more-intelligence-and-empathy">more empathetic</a>, and <a href="https://n.neurology.org/content/early/2013/07/03/WNL.0b013e31829c5e8a">stave off cognitive decline</a> by <a href="https://www.psychologytoday.com/intl/blog/the-athletes-way/201401/reading-fiction-improves-brain-connectivity-and-function">improving brain connectivity</a>. In essence, reading involves a <a href="https://neuro.hms.harvard.edu/harvard-mahoney-neuroscience-institute/brain-newsletter/and-brain-series/reading-and-brain">multitude of brain regions</a> including the temporal and frontal lobes. There&rsquo;s still a lot to be discovered about the human brain, but here&rsquo;s what we think we know. The frontal lobes <a href="https://www.healthline.com/human-body-maps/frontal-lobe">control important cognitive skills</a> like emotional expression, problem solving, memory, language, judgement, and sexual behaviors. The temporal lobes handle important functions such as <a href="https://www.health.qld.gov.au/abios/asp/btemporal_lobes">the encoding of memory, and processing emotions and visual perception</a>.</p>
<p>In other words, reading text - even on social media - stimulates your brain and makes you think about the information you&rsquo;re taking in. To react to words on a page, you first have to read them and form thoughts about them.</p>
<p>Unlike reading, looking at an image has a very different effect on your brain. <a href="https://neomam.com/interactive/13reasons/">Here&rsquo;s an infographic about infographics</a> that covers some of these effects. Basically, millennia of evolution have produced human brains hardwired to respond quickly to visual stimuli - in less than 1/10 of a second. As the infographic will literally show you, almost 50% of the brain is involved in visual processing, and 70% of all our sensory receptors are in our eyes. That&rsquo;s a lot of resources devoted to quickly processing visuals. Why could this be bad?</p>
<p>Unlike times past, we&rsquo;re no longer (day-to-day) concerned with spotting a tiger in the bushes about to pounce on us. The near-instant processing time needed to discern if that shivering tree branch is the just wind or impending mortal danger is <em>outdated</em> in our current living arrangements. Our brain, however, doesn&rsquo;t know that. It hasn&rsquo;t evolved faster than our technologies or society. The downside to this is that anyone with a little knowledge of this fundamental flaw in the human mind is able to exploit it.</p>
<p><a href="https://web.archive.org/web/20200719034830/https://thenextweb.com/dd/2014/05/21/importance-visual-content-deliver-effectively/">Advertisers</a> <a href="https://www.liveplan.com/blog/2016/01/scientific-reasons-why-you-should-present-your-data-visually/">call</a> <a href="https://www.fastcompany.com/3035856/why-were-more-likely-to-remember-content-with-images-and-video-infogr">this</a> <a href="https://www.canva.com/learn/visual-marketing/">exploitation</a>, &ldquo;<a href="https://blog.bufferapp.com/a-complete-guide-to-creating-awesome-visual-content">visual</a> <a href="https://web.archive.org/web/20200501165857/https://www.business2community.com/digital-marketing/visual-marketing-pictures-worth-60000-words-01126256">marketing</a>.&rdquo;</p>
<p>These linked articles are stuffed with the same factoids over and over again. &ldquo;The brain processes images 60,000 times faster than text.&rdquo; &ldquo;90% of the information sent to the brain is visual.&rdquo; Whether or not these numbers are accurate, it&rsquo;s clearly provable that visual marketing is on the whole more effective than advertisements without images. There&rsquo;s a reason for it, and it should scare you.</p>
<p>Unlike reading, which involves regions of your brain responsible for comprehension, decision making, and emotional control, <a href="https://doi.org/10.1073/pnas.95.5.2703">images are processed by different areas of the brain</a>. <a href="https://qbi.uq.edu.au/brain/brain-physiology/visual-perception">Visual input travels</a> from our eyes through our optic nerves to the thalamus (or LGN, Lateral Geniculate Nucleus) and the superior colliculus. From the thalamus, it proceeds to the visual cortex at the rear of our brains, where the <a href="https://en.wikipedia.org/wiki/Visual_system">image is processed</a>. Effectively, viewing images does not make us think in the same way that reading does. In other words, it&rsquo;s easy to do.</p>
<p>Let me be clear. This difference in the way words and images are processed is not, in itself, bad. A photo-centric social network is not, in itself, bad. Images and words alike have the power to evoke strong emotions, send powerful messages, spark revolutions, and spur progress. This is good&hellip; if it&rsquo;s used for good.</p>
<p>Instagram, a photo-centric network chock full of product placements, paid sponsorships, and outright advertisements, is a social network primarily designed to bypass your cognitive thinking and <em>sell you stuff.</em></p>
<p>I don&rsquo;t think Instagram started out with the same motivations it has now. Along with all the photo sharing networks that blossomed when Instagram first got popular, I still believe its initial vision was to make sharing photos with your friends fun and easy.</p>
<p>It just got too popular.</p>
<h2 id="why-centralized-networks-are-bad-for-your-health">Why centralized networks are bad for your health</h2>
<p>In the wake of privacy concerns over the last few years, new uproar over algorithm-driven timelines, and the #DeleteFacebook, #DeleteTwitter, and #DeleteInstagram movements, more people today are aware of how networks that make their money on your data are bad for your health. This is in part due to their centralized nature - one hierarchy of authority makes decisions for the whole system, and at the same time, has to support it. It&rsquo;s expensive to support millions of users, so it&rsquo;s no wonder that the network&rsquo;s main concern (and let&rsquo;s just consider the most innocent case) is to remain profitable.</p>
<p>What&rsquo;s a good way to remain profitable?</p>
<blockquote>
<p>Take a human desire, preferably one that has been around for a really long time&hellip; Identify that desire and use modern technology to take out steps. &ndash; Evan Williams, co-founder of Twitter and Blogger</p>
<p><em>Quoted in Wired article, 2013, &ldquo;<a href="https://www.wired.com/2013/09/ev-williams-xoxo/">Twitter Founder Reveals Secret Formula for Getting Rich Online</a>&rdquo;</em></p>
</blockquote>
<p>There&rsquo;s a book called <em>Hooked: How to Build Habit-Forming Products</em> which, if you&rsquo;re ever in the mood for a good horror flick, you should curl up in bed with some popcorn and read.</p>
<p>The book details a simple model for a habit-forming product. The model is cyclical, and has the following key points: a trigger, an action, variable reward, and investment. In summary, if a product can get you to think of it, leading to some action that is easier to do than to think about, give you a reward for that action <em>some of</em> the time, and then compel you to commit or invest in it - you&rsquo;re hooked.</p>
<p>If you&rsquo;re paying attention, you might notice I&rsquo;ve described Instagram. And Twitter. And Facebook. And every other social network.</p>
<p>There&rsquo;s a reason it&rsquo;s easy to use Instagram, easy to post a tweet, easy to browse Facebook. These products have been designed to make it easy for you to use them. They&rsquo;ve been designed to alter your behavior to better suit the product&rsquo;s goals.</p>
<blockquote>
<p>This industry employs some of the smartest people, thousands of Ph.D. designers, statisticians, engineers. They go to work every day to get us to do this one thing, to undermine our willpower. &ndash; James Williams, co-founder of <a href="https://humanetech.com/">Time Well Spent</a></p>
<p><em>Quoted in Nautilus article, 2017, &ldquo;<a href="https://nautil.us/modern-media-is-a-dos-attack-on-your-free-will-236806/">Modern Media Is a DoS Attack on Your Free Will</a>&rdquo;</em></p>
</blockquote>
<p>At the heart of the idea of getting you hooked is the concept of a <a href="https://www.nakedcapitalism.com/2018/01/social-media-dopamine-loop-role-software-engineer.html">dopamine feedback loop</a>. Dopamine, an <a href="https://en.wikipedia.org/wiki/Dopamine">organic chemical neurotransmitter</a> in your brain, is thought to be responsible for <a href="https://www.theguardian.com/technology/2018/mar/04/has-dopamine-got-us-hooked-on-tech-facebook-apps-addiction">allowing us to anticipate the reward to an action</a>. It inspires us to get a glass of water when we&rsquo;re thirsty, for example, and may help us to feel good when we take actions towards doing so. Where dopamine is so effectively misused is in the practice of providing <a href="https://techcrunch.com/2012/03/25/want-to-hook-your-users-drive-them-crazy/">variable rewards</a> to drive social media addiction.</p>
<p>Unlike getting a glass of water when you&rsquo;re thirsty, variable rewards are random. It&rsquo;s as if drinking water sometimes, but not always, cured your thirst. This effectively <a href="https://www.smh.com.au/world/social-media-destroying-society-with-dopaminedriven-feedback-loops-exfacebook-vp-20171213-h03jfo.html">programs your mind</a> to pursue the action that results in the unpredictable reward. Since getting the reward isn&rsquo;t guaranteed, you need to make more attempts to achieve success. Social media is designed to make these variable dopamine hits easy to obtain. It&rsquo;s designed to hijack your intellectual independence in order to keep you on the network.</p>
<p><em>Especially</em> when the main goal of a centralized social network is to make a profit, that network is exploiting evolutionary flaws in your brain to make that profit from you. You are literally being hacked.</p>
<p>Now combine this information with the knowledge of how a product comprised primarily of images bypasses your cognitive thinking. Not only are you being hacked, but your main defense system is being easily, laughably, circumvented.</p>
<p>Exploiting users is a particularly compelling temptation for any social networks under pressure to make a profit, and this pressure is amplified in organizations with a centralized structure. Not all centralized networks do this, but undoubtedly, the very successful ones do.</p>
<p>Decentralization is by no means a fix for exploitation and greed, but a decentralized social network might have a few things going for it.</p>
<h2 id="why-decentralized-networks-_might_-be-slightly-better-for-your-health">Why decentralized networks <em>might</em> be slightly better for your health</h2>
<p>The main issues present in Twitter, Facebook, and Instagram as pertains to social media addiction do not go away on decentralized networks. I&rsquo;m personally, currently, using both Twitter and Mastodon. The former is centralized and the latter is decentralized, but the the same motivations that could get me in trouble on one platform apply to both. Decentralization does not fix the problem.</p>
<p>It <em>might</em> help.</p>
<p>Unlike a centralized, single-hierarchy, definitely-for-profit social network, decentralization has one thing going for it: more people. Specifically, more instance owners who are in control of their instances.</p>
<p>Running a Mastodon instance is a responsibility, should you choose to accept it. Besides the server itself, instances require their own sets of rules and code of conduct, and like the often adopted <a href="https://mastodon.social/about/more">mastodon.social code of conduct</a>, it can be collaboratively drafted by the community. Mastodon provides instance owners with moderation tools and provides users with reporting tools, and there&rsquo;s an expectation that they&rsquo;ll both be used. As with other decentralized social networks, it is the responsibility of the instance owner to moderate and foster a social environment that serves the best interests of the instance users.</p>
<p>Instances typically run on donations, and in the grand scheme of things, are inexpensive to support. Decentralization means that instance owners individually have to bear smaller costs. There&rsquo;s no central body being pressured to make a profit in order to run servers that support millions of users. The effect of this many-owners structure is that decisions that concern any particular instance and rules that it might want to adopt are made by that instance&rsquo;s community, or the instance owner. If a user disagrees with the direction taken, they can communicate directly with the instance owner, or simply move to another instance. There&rsquo;s no &ldquo;take it or leave it,&rdquo; and no forced acceptance of terms. Users always have somewhere else to go.</p>
<p>This, in general, means that over many instances, and via many moderators, more people from diverse backgrounds with a collection of both overlapping and contrasting interests are able to have a voice in how the social network evolves.</p>
<p>If instance owners have their users&rsquo; best interests, not addiction, in mind; if moderators act responsibly, and according to their instance rules, moderate for good; and if a wide and varying selection of instances with differing interests, political viewpoints, and topics continue to be available; then decentralized social networks <em>might</em> be better for your health.</p>
<h2 id="where-this-leaves-pixelfed-and-all-social-networks">Where this leaves PixelFed (and all social networks)</h2>
<p>All social networks have the potential to do more good than harm, but it is up to those who control them to put in the constant, proactive effort required to make that happen. Twitter has recently been making some steps towards becoming a healthier network, like <a href="https://www.cnbc.com/2019/10/30/twitter-bans-political-ads-after-facebook-refused-to-do-so.html">banning political ads</a> and <a href="https://techcrunch.com/2020/02/04/twitters-policy-on-deepfakes-and-manipulated-media-will-only-remove-harmful-tweets-including-voter-suppression/">highlighting manipulated media</a>. I think they&rsquo;re ahead of the curve. With decentralized social networks, there&rsquo;s at least more chances for the possibility that instance owners truly want to do more good than harm with their own little piece of the whole.</p>
<p>While photo sharing networks will, by their essential nature, bypass cognitive thinking and have an advantage over their users that way, there are many design considerations that PixelFed can implement in order to make the network healthier. Features such as comments, likes, timelines, and push notifications can be designed to provide utility more than drive addiction, and there are <a href="https://qz.com/1264547/facebooks-problems-can-be-solved-with-design/">designers more qualified than I</a> who can tell you how.</p>
<p>These networks will have to constantly resist the temptation to take the easy route. They will have to work to avoid success based on the exploitation of their users&rsquo; desires to chase the easy dopamine hit. They will have to prioritize the ability of the social network to add real value to the lives of its users - at the expense of its own potential to garner mindless, meaningless popularity.</p>
<p>This is in no way a condemnation of PixelFed or any other decentralized photo sharing network. Personally, I sincerely hope they succeed in giving users a healthy, safe, and free-as-in-freedom network for sharing photos with friends, and with the rest of the federated community. It will require considered design with mental health at the forefront; the active, caring effort of moderators and instance owners; and ongoing collaboration from the federated community at large to work together to build for the greater good.</p>
<p>A photo-sharing social media network that does more good than harm? It&rsquo;s possible. But it won&rsquo;t be easy.</p>
]]></content></entry><entry><title type="html">The past ten years, or, how to get better at anything</title><link href="https://victoria.dev/archive/the-past-ten-years-or-how-to-get-better-at-anything/"/><id>https://victoria.dev/archive/the-past-ten-years-or-how-to-get-better-at-anything/</id><author><name>Victoria Drake</name></author><published>2019-12-31T08:27:31-04:00</published><updated>2019-12-31T08:27:31-04:00</updated><content type="html"><![CDATA[<p>If you want to get better at anything:</p>
<ol>
<li>Solve your own problems,</li>
<li>Write about it,</li>
<li>Teach others.</li>
</ol>
<h2 id="1-searching-a-decade-ago">1. Searching, a decade ago</h2>
<p>I was a young graduate with newly-minted freedoms, and I was about to fall in love. I had plenty of imagination, a couple handfuls of tenacity, and no sense of direction at all.</p>
<p>For much of my youth, when I encountered a problem, I just sort of bumped up against it. I tried using whatever was in my head from past experiences or my own imagination to find a solution. For some problems, like managing staff duties at work, my experience was sufficient guidance. For other, more complicated problems, it wasn&rsquo;t.</p>
<p>When you don&rsquo;t have a wealth of experience to draw upon, relying on it is a poor strategy. Like many people at my age then, I thought I knew enough. Like many people at my age now, I recognize how insufficient &ldquo;enough&rdquo; can be. A lack of self-directed momentum meant being dragged in any direction life&rsquo;s currents took me. When falling in love turned out to mean falling from a far greater height than I had anticipated, I tumbled on, complacent. When higher-ups at work handed me further responsibilities, I accepted them without considering if I wanted them at all. When, inevitably, life became more and more complicated, I encountered even more problems I didn&rsquo;t know how to solve. I felt stuck.</p>
<p>Though I was morbidly embarrassed about it at the time, I&rsquo;m not shy to say it now. At one point, it had to be pointed out to me that I could search the Internet for the solution to any of my problems. Anything I wanted to solve - interactions with people at work, a floundering relationship, or the practicalities of filing taxes - I was lucky enough to have the greatest collection of human knowledge ever assembled at my disposal.</p>
<p>Instead of bumbling along in the floatsam of my own trial and error, I started to take advantage of the collective experiences of all those who have been here before me. They weren&rsquo;t always right, and I often found information only somewhat similar to my own experience. Still, it always got me moving in the right direction. Eventually, I started to steer.</p>
<p>There&rsquo;s a learning curve, even when just searching for a problem. Distilling the jumble of confusion in your head to the right search terms is a learned skill. It helped me to understand <a href="https://www.google.com/search/howsearchworks/crawling-indexing/">how search engines like Google work</a>:</p>
<blockquote>
<p>We use software known as web crawlers to discover publicly available webpages. Crawlers look at webpages and follow links on those pages, much like you would if you were browsing content on the web. They go from link to link and bring data about those webpages back to Google’s servers&hellip;</p>
<p>When crawlers find a webpage, our systems render the content of the page, just as a browser does. We take note of key signals — from keywords to website freshness — and we keep track of it all in the Search index.</p>
</blockquote>
<p>Sometimes, I find what I need by using the right keyword. Other times, I discover the keyword by searching for text that might surround it on the content of the page. For software development, I search for the weirdest word or combination of words attached to what I&rsquo;m trying to learn. I rarely find whole solutions in my search results, but I always find direction for solving the problem myself.</p>
<p>Solving my own problems, even just a few little ones at a time, gave me confidence and built momentum. I began to pursue the experiences I wanted, instead of waiting for experiences to happen to me.</p>
<h2 id="2-updating-the-internet-some-years-ago">2. Updating the Internet, some years ago</h2>
<p>I&rsquo;d solved myself out of a doomed relationship and stagnant job. I found myself, rather gleefully, country-hopping with just <a href="https://heronebag.com">one backpack</a> of possessions. I met, though I didn&rsquo;t know it at the time, my future husband. I found a new sense of freedom, of having options, that I knew I never wanted to give up. I had to find a means to sustain myself by working remotely.</p>
<p>When I first tried to make a living on the Internet, I felt like a right amateur. Sitting on the bed, hunched over my laptop, I started a crappy Wordpress blog with a modified theme that didn&rsquo;t entirely work. I posted about how I tried and failed to start a dropshipping business. My site was terrible, and I knew it. My first forays into being a &ldquo;real&rdquo; developer were to solve my own problems: how to get my blog working, how to set up a custom domain, how to get and use a security certificate. I found some guidance in blogs and answers that others had written, but much of it was outdated, or not entirely correct. Still, it helped me.</p>
<p>I can&rsquo;t imagine a world in which people did nothing to pass on their knowledge to future generations. Our stories are all we have beyond instinct and determination.</p>
<p>I stopped posting about dropshipping and started writing about the technical problems I was solving. I wrote about what I tried, and ultimately what worked. I started hearing from people who thanked me for explaining the solution they were looking for. Even in posts where all I&rsquo;d done was link to the correct set of instructions on some other website, people thanked me for leading them to it. I still thought my website was terrible, but I realized I was doing something useful. The more problems I solved, the better I got at solving them, and the more I wrote about it in turn.</p>
<p>One day, someone offered me money for one of my solutions. To my great delight, they weren&rsquo;t the last to do so.</p>
<p>As I built up my skills, I started taking on more challenging offers to solve problems. I discovered, as others have before me, that especially in software development, not every solution is out there waiting for you. The most frustrating part of working on an unsolved problem is that, at least to your knowledge, there&rsquo;s no one about to tell you how to solve it. If you&rsquo;re lucky, you&rsquo;ve at least got a heading from someone&rsquo;s cold trail in an old blog post. If you&rsquo;re lucky and tenacious, you&rsquo;ll find a working solution.</p>
<p>Don&rsquo;t leave it scribbled in the corner of a soon-forgotten notepad, never to ease the path of someone who comes along later. Update that old blog post by commenting on it, or sending a note to the author. Put your solution on the Internet, somewhere. Ideally, blog about it yourself in as much detail as you can recall. Some of the people who find your post might have the same problem, and might even be willing to pay you to solve it. And, if my own experience and some scattered stories hold true, one of the people to who&rsquo;ll come along later, looking for that same solution, will be you.</p>
<h2 id="3-paying-it-forwards-backwards-and-investing-two-years-ago">3. Paying it forwards, backwards, and investing; two years ago</h2>
<p>Already being familiar with how easy it is to stop steering and start drifting, I sought new ways to challenge myself and my skills. I wanted to do more than just sustain my lifestyle. I wanted to offer something to others; something that mattered.</p>
<p>A strange thing started happening when I decided, deliberately, to write an in-depth technical blog about topics I was only beginning to become familiar with. I started to deeply understand some fundamental computer science topics - and trust me, that was strange enough - but odder than that was that others started to see me as a resource. People asked me questions because they thought I had the answers. I didn&rsquo;t, at least, not always - but I knew enough now to not let that stop me. I went to find the answers, to test and understand them, and then I wrote about them to teach those who had asked. I hardly noticed, along the way, that I was learning too.</p>
<p>When someone&rsquo;s outdated blog post leads you to an eventual solution, you can pay them back by posting an update, or blogging about it yourself. When you solve an unsolved problem, you pay it forward by recording that solution for the next person who comes along (sometimes you). In either case, by writing about it - honestly, and with your best effort to be thorough and correct - you end up investing in yourself.</p>
<p>Explaining topics you&rsquo;re interested in to other people helps you find the missing pieces in your own knowledge. It helps you fill those gaps with learning, and integrate the things you learn into a new, greater understanding. Teaching something to others helps you become better at it yourself. Getting better at something - anything - means you have more to offer.</p>
<h2 id="the-past-decade-and-the-next-decade">The past decade, and the next decade</h2>
<p>It&rsquo;s the end of a decade. I went from an aimless drift through life to being captain of my ship. I bettered my environment, learned new skills, made myself a resource, and became a wife to my best friend. I&rsquo;m pretty happy with all of it.</p>
<p>It&rsquo;s the end of 2019. Despite a whole lot of life happening just this year, I&rsquo;ve written one article on this blog for each week since I started in July. That&rsquo;s 23 articles for 23 weeks, plus one Christmas bonus. I hear from people almost every day who tell me that an article I wrote was helpful to them, and it makes me happy and proud to think that I&rsquo;ve been doing something that matters. The first week of January will make this blog two years old.</p>
<p>The past several months have seen me change tack, slightly. I&rsquo;ve become very interested in cybersecurity, and have been lending my skills to the Open Web Application Security Project. I&rsquo;m now an author and maintainer of the <a href="https://github.com/OWASP/wstg">Web Security Testing Guide</a>, version 5. I&rsquo;m pretty happy with that, too.</p>
<p>Next year, I&rsquo;ll be posting a little less, though writing even more, as I pursue an old dream of publishing a book, as well as develop my new cybersecurity interests. I aim to get better at quite a few things. Thankfully, I know just how to do it - and now, so do you:</p>
<ol>
<li>Solve your own problems,</li>
<li>Write about it,</li>
<li>Teach others.</li>
</ol>
<p>Have a very happy new decade, dear reader.</p>
]]></content></entry><entry><title type="html">Three healthy cybersecurity habits</title><link href="https://victoria.dev/archive/three-healthy-cybersecurity-habits/"/><id>https://victoria.dev/archive/three-healthy-cybersecurity-habits/</id><author><name>Victoria Drake</name></author><published>2019-12-26T08:27:31-04:00</published><updated>2019-12-26T08:27:31-04:00</updated><content type="html"><![CDATA[<p>In a similar fashion to everyone getting the flu now and again, the risk of catching a cyberattack is a common one. Both a sophisticated social engineering attack or grammatically-lacking email phishing scam can cause real damage. No one who communicates over the Internet is immune.</p>
<p>Like proper hand washing and getting a flu shot, good habits can lower your risk of inadvertently allowing cybergerms to spread. Since the new year is an inspiring time for beginning new habits, I offer a few suggestions for ways to help protect yourself and those around you.</p>
<h2 id="1-get-a-follow-up">1. Get a follow-up</h2>
<p>Recognizing a delivery method for cyberattack is getting more difficult. Messages with malicious links do not always come from strangers. They may appear to be routine communications, or seem to originate from someone you know or work with. Attacks use subtle but deeply-engrained cognitive biases to override your common sense. Your natural response ensures you click.</p>
<p>Thankfully, there&rsquo;s a simple low-tech habit you can use to deter these attacks: before you act, follow-up.</p>
<p>You may get an email from a friend that needs help, or from your boss who&rsquo;s about to get on a plane. It could be as enticing and mysterious as a direct message from an acquaintance who sends a link asking, &ldquo;Lol. Is this you?&rdquo; It takes presence of mind to override the panic these attacks prey on, but the deterrent itself is quick and straightforward. Send a text message, pick up the phone and call, or walk down the hall, and ask, &ldquo;Did you send me this?&rdquo;</p>
<p>If the message is genuine, there&rsquo;s no harm in a few extra minutes to double check. If it&rsquo;s not, you&rsquo;ll immediately alert the originating party that they may be compromised, and you may have deterred a cyberattack!</p>
<h2 id="2-use-and-encourage-others-to-use-end-to-end-encrypted-messaging">2. Use, and encourage others to use, end-to-end encrypted messaging</h2>
<p>When individuals in a neighborhood get the flu shot, others in that neighborhood are safer for it. Encryption is similarly beneficial. Encourage your friends, coworkers, and Aunt Matilda to switch to an app like Signal. By doing so, you&rsquo;ll reduce everyone&rsquo;s exposure to more exploitable messaging systems.</p>
<p>This doesn&rsquo;t mean that you must stop using other methods of communication entirely. Instead, think of it as a hierarchy. Use Signal for important messages that should be trusted, like requests for money or making travel arrangements. Use all other methods of messaging, like SMS or social sites, only for &ldquo;unimportant&rdquo; communications. Now, if requests or links that seem important come to you through your unimportant methods, you&rsquo;ll be all the more likely to second-guess them.</p>
<h2 id="3-dont-put-that-dirty-usb-plug-into-your-">3. Don&rsquo;t put that dirty USB plug into your ***</h2>
<p>You wouldn&rsquo;t brush your teeth with a toothbrush you found on the sidewalk. Why would you plug in a USB device if you don&rsquo;t know where it&rsquo;s been?! While we might ascribe <a href="https://en.wikipedia.org/wiki/2008_cyberattack_on_United_States">putting a random found USB drive in your computer</a> to a clever exploitation of natural human curiosity, we&rsquo;re no sooner likely to suspect using <a href="https://www.howtogeek.com/444267/how-safe-are-public-charging-stations/">a public phone-charging station</a> or <a href="https://www.theverge.com/2019/8/15/20807854/apple-mac-lightning-cable-hack-mike-grover-mg-omg-cables-defcon-cybersecurity">a USB cable</a> we bought ourselves. Even seemingly-innocuous USB <a href="https://www.cbsnews.com/news/why-your-usb-device-is-a-security-risk/">peripherals</a> or <a href="https://www.us-cert.gov/ncas/current-activity/2010/03/08/Energizer-DUO-USB-Battery-Charger-Software-Allows-Remote-System">rechargeable</a> devices can be a risk.</p>
<p>Unlike email and some file-sharing services that scan and filter files before they reach your computer, plugging in via USB is as direct and <a href="https://www.wired.com/2014/07/usb-security/">unprotected</a> as connection gets. Once this connection is made, the user doesn&rsquo;t need to do anything else for a whole host of bad things to happen. Through USB connections, problems like malware and ransomware can easily infect your computer or phone.</p>
<p>There&rsquo;s no need to swear off the convenience of USB connectivity, or to avoid these devices altogether. Instead of engaging in questionable USB behavior, don&rsquo;t cheap out on USB devices and cables. If it&rsquo;s going to get plugged into your computer, ensure you&rsquo;re being extra cautious. Buy it from the manufacturer (like the Apple Store) or from a reputable company or reseller with supply chain control. When juicing up USB-rechargeables, don&rsquo;t plug them into your computer. Use <a href="https://heronebag.com/blog/40-hours-drive-time-my-road-trip-charging-essentials/">a wall charger with a USB port</a> instead.</p>
<h2 id="practice-healthy-cybersecurity-habits">Practice healthy cybersecurity habits</h2>
<p>Keeping your devices healthy and happy is a matter of practicing good habits. Like battling the flu, good habits can help protect yourself and those around you. Incorporate some conscientious cybersecurity practices in your new year resolutions - or start them right away.</p>
<p>Have a safe and happy holiday!</p>
]]></content></entry><entry><title type="html">Concurrency, parallelism, and the many threads of Santa Claus 🎅</title><link href="https://victoria.dev/archive/concurrency-parallelism-and-the-many-threads-of-santa-claus/"/><id>https://victoria.dev/archive/concurrency-parallelism-and-the-many-threads-of-santa-claus/</id><author><name>Victoria Drake</name></author><published>2019-12-23T19:29:01-05:00</published><updated>2019-12-23T19:29:01-05:00</updated><content type="html"><![CDATA[<p>Consider the following: Santa brings toys to all the good girls and boys.</p>
<p>There are <a href="https://en.wikipedia.org/wiki/Demographics_of_the_world#Current_population_distribution">7,713,468,100 people</a> in the world in 2019, <a href="https://en.wikipedia.org/wiki/Demographics_of_the_world#Age_structure">around 26.3%</a> of which are under 15 years old. This works out to 2,028,642,110 children (persons under 15 years of age) in the world this year.</p>
<p>Santa doesn&rsquo;t seem to visit children of every religion, so we&rsquo;ll generalize and only include Christians and non-religious folks. Collectively that makes up <a href="https://en.wikipedia.org/wiki/List_of_religious_populations#Adherent_estimates_in_2019">approximately 44.72%</a> of the population. If we assume that all kids take after their parents, then 907,208,751.6 children would appear to be Santa-eligible.</p>
<p>What percentage of those children are good? It&rsquo;s impossible to know; however, we can work on a few assumptions. One is that Santa Claus functions more on optimism than economics and would likely have prepared for the possibility that every child is a good child in any given year. Thus, he would be prepared to give a toy to every child. Let&rsquo;s assume it&rsquo;s been a great year and that all 907,208,751.6 children are getting toys.</p>
<p>That&rsquo;s a lot of presents, and, as we know, they&rsquo;re all made by Santa&rsquo;s elves at his North <del>China</del> Pole workshop. Given that there are 365 days in a year and one of them is Christmas, let&rsquo;s assume that Santa&rsquo;s elves collectively have 364 days to create and gift wrap 907,208,752 (rounded up) presents. That works out to 2,492,331.74 presents per day.</p>
<p>Almost two-and-a-half million presents per day is a heavy workload for any workshop. Let&rsquo;s look at two paradigms that Santa might employ to hit this goal: concurrency, and parallelism.</p>
<h2 id="a-sequential-process">A sequential process</h2>
<p>Suppose that Santa&rsquo;s workshop is staffed by exactly one, very hard working, very tired elf. The production of one present involves four steps:</p>
<ol>
<li>Cutting wood</li>
<li>Assembly and glueing</li>
<li>Painting</li>
<li>Gift-wrapping</li>
</ol>
<p>With a single elf, only one step for one present can be happening at any instance in time. If the elf were to produce one present at a time from beginning to end, that process would be executed <em>sequentially</em>. It&rsquo;s not the most efficient method for producing two-and-a-half million presents per day; for instance, the elf would have to wait around doing nothing while the glue on the present was drying before moving on to the next step.</p>
<p><img src="sequence.png" alt="Illustration of sequence"></p>
<h2 id="concurrency">Concurrency</h2>
<p>In order to be more efficient, the elf works on all presents <em>concurrently</em>.</p>
<p>Instead of completing one present at a time, the elf first cuts all the wood for all the toys, one by one. When everything is cut, the elf assembles and glues the toys together, one after the other. This <a href="https://en.wikipedia.org/wiki/Concurrent_computing">concurrent processing</a> means that the glue from the first toy has time to dry (without needing more attention from the elf) while the remaining toys are glued together. The same goes for painting, one toy at a time, and finally wrapping.</p>
<p><img src="concurrency.png" alt="Illustration of concurrency"></p>
<p>Since one elf can only do one task at a time, a single elf is using the day as efficiently as possible by concurrently producing presents.</p>
<h2 id="parallelism">Parallelism</h2>
<p>Hopefully, Santa&rsquo;s workshop has more than just one elf. With more elves, more toys can be built simultaneously over the course of a day. This simultaneous work means that the presents are being produced in <em>parallel</em>. <a href="https://en.wikipedia.org/wiki/Parallel_computing">Parallel processing</a> carried out by multiple elves means more work happens at the same time.</p>
<p><img src="parallel.png" alt="Illustration of parallel processes"></p>
<p>Elves working in parallel can also employ concurrency. One elf can still tackle only one task at a time, so it&rsquo;s most efficient to have multiple elves concurrently producing presents.</p>
<p>Of course, if Santa&rsquo;s workshop has, say, two-and-a-half million elves, each elf would only need to finish a maximum of one present per day. In this case, working sequentially doesn&rsquo;t detract from the workshop&rsquo;s efficiency. There would still be 7,668.26 elves left over to fetch coffee and lunch.</p>
<h2 id="santa-claus-and-threading">Santa Claus, and threading</h2>
<p>After all the elves&rsquo; hard work is done, it&rsquo;s up to Santa Claus to deliver the presents &ndash; all 907,208,752 of them.</p>
<p>Santa doesn&rsquo;t need to make a visit to every kid; just to the one household tree. So how many trees does Santa need to visit? Again with broad generalization, we&rsquo;ll say that the average number of children per household worldwide is <a href="https://en.wikipedia.org/wiki/Demographics_of_the_world#Total_fertility_rate">2.45, based on the year&rsquo;s predicted fertility rates</a>. That makes 370,289,286.4 houses to visit. Let&rsquo;s round that up to 370,289,287.</p>
<p>How long does Santa have? The lore says one night, which means one earthly rotation, and thus 24 hours. <a href="https://www.noradsanta.org/en/">NORAD confirms</a>.</p>
<p>This means Santa must visit 370,289,287 households in 24 hours (86,400 seconds), at a rate of 4,285.75 households per second, nevermind the time it takes to put presents under the tree and grab a cookie.</p>
<p>Clearly, Santa doesn&rsquo;t exist in our dimension. This is especially likely given that despite being chubby and plump, he fits down a chimney (with a lit fire, while remaining unhurt) carrying a sack of toys containing presents for all the household&rsquo;s children. We haven&rsquo;t even considered the fact that his sleigh carries enough toys for every believing boy and girl around the world, and flies.</p>
<p>Does Santa exist outside our rules of physics? How could one entity manage to travel around the world, delivering packages, in under 24 hours at a rate of 4,285.75 households per second, and still have time for milk and cookies and kissing mommy?</p>
<p>One thing is certain: Santa uses the Internet. No other technology has yet enabled packages to travel quite so far and quite so quickly. Even so, attempting to reach upwards of four thousand households per second is no small task, even with even the best gigabit Internet hookup the North Pole has to offer. How might Santa increase his efficiency?</p>
<p>There&rsquo;s clearly only one logical conclusion to this mystery: Santa Claus is a multithreaded process.</p>
<h2 id="a-single-thread">A single thread</h2>
<p>Let&rsquo;s work outward. Think of a <a href="https://en.wikipedia.org/wiki/Thread_(computing)">thread</a> as one particular task, or the most granular sequence of instructions that Santa might execute. One thread might execute the task, <code>put present under tree</code>. A thread is a component of a process, in this case, Santa&rsquo;s process of delivering presents.</p>
<p>If Santa Claus is <a href="https://en.wikipedia.org/wiki/Thread_(computing)#Single_threading">single-threaded</a>, he, as a process, would only be able to accomplish one task at a time. Since he&rsquo;s old and a bit forgetful, he probably has a set of instructions for delivering presents, as well as a schedule to abide by. These two things guide Santa&rsquo;s thread until his process is complete.</p>
<p><img src="single.png" alt="A single Santa Claus emoji"></p>
<p>Single-threaded Santa Claus might work something like this:</p>
<ol>
<li>Land sleigh at Timmy&rsquo;s house</li>
<li>Get Timmy&rsquo;s present from sleigh</li>
<li>Enter house via chimney</li>
<li>Locate Christmas tree</li>
<li>Place Timmy&rsquo;s present under Christmas tree</li>
<li>Exit house via chimney</li>
<li>Take off in sleigh</li>
</ol>
<p>Rinse and repeat&hellip; another 370,289,286 times.</p>
<h2 id="multithreading">Multithreading</h2>
<p><a href="https://en.wikipedia.org/wiki/Thread_(computing)#Multithreading">Multithreaded</a> Santa Claus, by contrast, is the <a href="https://dc.fandom.com/wiki/Jonathan_Osterman_(Watchmen)">Doctor Manhattan</a> of the North Pole. There&rsquo;s still only one Santa Claus in the world; however, he has the amazing ability to multiply his consciousness and accomplish multiple instruction sets of tasks simultaneously. These additional task workers, or worker threads, are created and controlled by the main process of Santa delivering presents.</p>
<p><img src="cover.png" alt="Multiple Santa threads"></p>
<p>Each worker thread acts independently to complete its instructions. Since they all belong to Santa&rsquo;s consciousness, they share Santa&rsquo;s memory and know everything that Santa knows, including what planet they&rsquo;re running around on, and where to get the presents from.</p>
<p>With this shared knowledge, each thread is able to execute its set of instructions in parallel with the other threads. This multithreaded parallelism makes the one and only Santa Claus as efficient as possible.</p>
<p>If an average present delivery run takes an hour, Santa need only spawn 4,286 worker threads. With each making one delivery trip per hour, Santa will have completed all 370,289,287 trips by the end of the night.</p>
<p>Of course, in theory, Santa could even spawn 370,289,287 worker threads, each flying to one household to deliver presents for all the children in it! That would make Santa&rsquo;s process extremely efficient, and also explain how he manages to consume all those milk-dunked cookies without getting full. 🥛🍪🍪🍪</p>
<h2 id="an-efficient-and-merry-multithreaded-christmas">An efficient and merry multithreaded Christmas</h2>
<p>Thanks to modern computing, we now finally understand how Santa Claus manages the seemingly-impossible task of delivering toys to good girls and boys the world-over. From my family to yours, I hope you have a wonderful Christmas. Don&rsquo;t forget to hang up your stockings on the router shelf.</p>
<p>Of course, none of this explains how reindeer manage to fly.</p>
]]></content></entry><entry><title type="html">Word bugs in software documentation and how to fix them</title><link href="https://victoria.dev/archive/word-bugs-in-software-documentation-and-how-to-fix-them/"/><id>https://victoria.dev/archive/word-bugs-in-software-documentation-and-how-to-fix-them/</id><author><name>Victoria Drake</name></author><published>2019-12-18T09:01:23-04:00</published><updated>2019-12-18T09:01:23-04:00</updated><content type="html"><![CDATA[<p>I&rsquo;ve been an editor longer than I&rsquo;ve been a developer, so this topic for me is a real root issue. 🥁 When I see a great project with poorly-written docs, it hits close to <code>/home</code>. Okay, okay, I&rsquo;m done.</p>
<p>I help the <a href="https://github.com/OWASP">Open Web Application Security Project (OWASP)</a> with their <a href="https://github.com/OWASP/wstg">Web Security Testing Guide (WSTG)</a>. I was recently tasked with writing a <a href="https://github.com/OWASP/wstg/blob/master/style_guide.md">style guide</a> and article template that show how to write technical instruction for testing software applications.</p>
<p>I thought parts of the guide would benefit more people than just OWASP&rsquo;s contributors, so I&rsquo;m sharing some here.</p>
<p>Many of the projects I participate in are open source. This is a wonderful way for people to share solutions and to build on each others&rsquo; ideas. Unfortunately, it&rsquo;s also a great way for misused and non-existent words to catch on. Here&rsquo;s an excerpt of the guide with some mistakes I&rsquo;ve noticed and how you can fix them in your technical documents.</p>
<hr>
<h2 id="use-correct-words">Use Correct Words</h2>
<p>The following are frequently misused words and how to correct them.</p>
<h3 id="_andor_"><em>and/or</em></h3>
<p>While sometimes used in legal documents, <em>and/or</em> leads to ambiguity and confusion in technical writing. Instead, use <em>or</em>, which in the English language includes <em>and</em>. For example:</p>
<blockquote>
<p>Bad: &ldquo;The code will output an error number and/or description.&rdquo;
Good: &ldquo;The code will output an error number or description.&rdquo;</p>
</blockquote>
<p>The latter sentence does not exclude the possibility of having both an error number and description.</p>
<p>If you need to specify all possible outcomes, use a list:</p>
<blockquote>
<p>&ldquo;The code will output an error number, or a description, or both.&rdquo;</p>
</blockquote>
<h3 id="_frontend-backend_"><em>frontend, backend</em></h3>
<p>While it&rsquo;s true that the English language evolves over time, these are not yet words.</p>
<p>When referring to nouns, use <em>front end</em> and <em>back end</em>. For example:</p>
<blockquote>
<p>Security is equally important on the front end as it is on the back end.</p>
</blockquote>
<p>As a descriptive adverb, use the hyphenated <em>front-end</em> and <em>back-end</em>.</p>
<blockquote>
<p>Both front-end developers and back-end developers are responsible for application security.</p>
</blockquote>
<h3 id="_whitebox_-_blackbox_-_greybox_"><em>whitebox</em>, <em>blackbox</em>, <em>greybox</em></h3>
<p>These are not words.</p>
<p>As nouns, use <em>white box</em>, <em>black box</em>, and <em>grey box</em>. These nouns rarely appear in connection with cybersecurity.</p>
<blockquote>
<p>My cat enjoys jumping into that grey box.</p>
</blockquote>
<p>As adverbs, use the hyphenated <em>white-box</em>, <em>black-box</em>, and <em>grey-box</em>. Do not use capitalization unless the words are in a title.</p>
<blockquote>
<p>While white-box testing involves knowledge of source code, black-box testing does not. A grey-box test is somewhere in-between.</p>
</blockquote>
<h3 id="_ie_-_eg_"><em>ie</em>, <em>eg</em></h3>
<p>These are letters.</p>
<p>The abbreviation <em>i.e.</em> refers to the Latin <em>id est</em>, which means &ldquo;in other words.&rdquo; The abbreviation <em>e.g.</em> is for <em>exempli gratia</em>, translating to &ldquo;for example.&rdquo; To use these in a sentence:</p>
<blockquote>
<p>Write using proper English, i.e. correct spelling and grammar. Use common words over uncommon ones, e.g. &ldquo;learn&rdquo; instead of &ldquo;glean.&rdquo;</p>
</blockquote>
<h3 id="_etc_"><em>etc</em></h3>
<p>These are also letters.</p>
<p>The Latin phrase <em>et cetera</em> translates to &ldquo;and the rest.&rdquo; It is abbreviated <em>etc.</em> and typically placed at the end of a list that seems redundant to complete:</p>
<blockquote>
<p>WSTG authors like rainbow colors, such as red, yellow, green, etc.</p>
</blockquote>
<p>In technical writing, the use of <em>etc.</em> is problematic. It assumes the reader knows what you&rsquo;re talking about, and they may not. Violet is one of the colors of the rainbow, but the example above does not explicitly tell you if violet is a color that WSTG authors like.</p>
<p>It is better to be explicit and thorough than to make assumptions of the reader. Only use <em>etc.</em> to avoid completing a list that was given in full earlier in the document.</p>
<h3 id="__-ellipsis"><em>&hellip;</em> (ellipsis)</h3>
<p>The ellipsis punctuation mark can indicate that words have been left out of a quote:</p>
<blockquote>
<p>Linus Torvalds once said, &ldquo;Once you realize that documentation should be laughed at&hellip; THEN, and only then, have you reached the level where you can safely read it and try to use it to actually implement a driver.&rdquo;</p>
</blockquote>
<p>As long as the omission does not change the meaning of the quote, this is acceptable usage of ellipsis in the WSTG.</p>
<p>All other uses of ellipsis, such as to indicate an unfinished thought, are not.</p>
<h3 id="_ex_"><em>ex</em></h3>
<p>While this is a word, it is likely not the word you are looking for. The word <em>ex</em> has particular meaning in the fields of finance and commerce, and may refer to a person if you are discussing your past relationships. None of these topics should appear in the WSTG.</p>
<p>The abbreviation <em>ex.</em> may be used to mean &ldquo;example&rdquo; by lazy writers. Please don&rsquo;t be lazy, and write <em>example</em> instead.</p>
<hr>
<h2 id="go-forth-and-write-docs">Go forth and write docs</h2>
<p>If these reminders are helpful, please share them freely and use them when writing your own READMEs and documentation! If there&rsquo;s some I&rsquo;ve missed, I&rsquo;d love to know.</p>
<p>And if you&rsquo;re here for the comments&hellip;</p>
<p><img src="crowder-change-my-mind.png#center" alt="Change my mind meme"></p>
<p>There are none on my blog. You can still <a href="/contact">@ me</a>.</p>
<p>If you&rsquo;d like to help contribute to the OWASP WSTG, please read <a href="https://github.com/OWASP/wstg/blob/master/CONTRIBUTING.md">the contribution guide</a>. See the <a href="https://github.com/OWASP/wstg/blob/master/style_guide.md">full style guide here</a>.</p>
]]></content></entry><entry><title type="html">Secure web forms for the front-end developer</title><link href="https://victoria.dev/archive/secure-web-forms-for-the-front-end-developer/"/><id>https://victoria.dev/archive/secure-web-forms-for-the-front-end-developer/</id><author><name>Victoria Drake</name></author><published>2019-12-11T08:27:31-04:00</published><updated>2019-12-11T08:27:31-04:00</updated><content type="html"><![CDATA[<p>While cybersecurity is often thought of in terms of databases and architecture, much of a strong security posture relies on elements in the domain of the front-end developer. For certain potentially devastating vulnerabilities like <a href="https://owasp.org/www-project-top-ten/OWASP_Top_Ten_2017/Top_10-2017_A1-Injection">SQL injection</a> and <a href="https://owasp.org/www-project-top-ten/OWASP_Top_Ten_2017/Top_10-2017_A7-Cross-Site_Scripting_(XSS)">Cross-Site Scripting (XSS)</a>, a well-considered user interface is the first line of defense.</p>
<p>Here are a few areas of focus for front-end developers who want to help fight the good fight.</p>
<h2 id="control-user-input">Control user input</h2>
<p>A whole whack of <a href="/blog/sql-injection-and-xss-what-white-hat-hackers-know-about-trusting-user-input/">crazy things</a> can happen when developers build a form that fails to control user input. To combat vulnerabilities like injection, it&rsquo;s important to validate or sanitize user input.</p>
<p>Input can be validated by constraining it to known values, such as by using <a href="https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/HTML5/Constraint_validation#Semantic_input_types">semantic input types</a> or <a href="https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/HTML5/Constraint_validation#Validation-related_attributes">validation-related attributes</a> in forms. Frameworks like <a href="https://www.djangoproject.com/">Django</a> also help by providing <a href="https://docs.djangoproject.com/en/3.0/ref/models/fields/#field-types">field types</a> for this purpose. Sanitizing data can be done by removing or replacing contextually-dangerous characters, such as by using a whitelist or escaping the input data.</p>
<p>While it may not be intuitive, even data that a user submits to their own area on a site should be validated. One of the fastest viruses to proliferate was the <a href="https://en.wikipedia.org/wiki/Samy_(computer_worm)">Samy worm</a> on MySpace (yes, I&rsquo;m old), thanks to code that Samy Kamkar was able to inject into his own profile page. Don&rsquo;t directly return any input to your site without thorough validation or santization.</p>
<p>For some further guidance on battling injection attacks, see the <a href="https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Injection_Prevention_Cheat_Sheet.md">OWASP Injection Prevention Cheat Sheet</a>.</p>
<h2 id="beware-hidden-fields">Beware hidden fields</h2>
<p>Adding <code>type=&quot;hidden&quot;</code> is an enticingly convenient way to hide sensitive data in pages and forms, but unfortunately not an effective one. With tools like <a href="https://www.zaproxy.org/">ZapProxy</a> and even inspection tools in plain ol&rsquo; web browsers, users can easily click to reveal tasty bits of invisible information. Hiding checkboxes can be a neat hack for creating CSS-only switches, but hidden fields do little to contribute to security.</p>
<h2 id="carefully-consider-autofill-fields">Carefully consider autofill fields</h2>
<p>When a user chooses to give you their <a href="https://en.wikipedia.org/wiki/Personal_data">Personally Identifiable Information</a> (PII), it should be a conscious choice. Autofill form fields can be convenient - for both users and attackers. <a href="https://freedom-to-tinker.com/2017/12/27/no-boundaries-for-user-identities-web-trackers-exploit-browser-login-managers/">Exploits using hidden fields can harvest PII</a> previously captured by an autocomplete field.</p>
<p>Many users aren&rsquo;t even aware what information their browser&rsquo;s autofill has stored up. Use these fields sparingly, and disable autofilled forms for particularly sensitive data.</p>
<p>It&rsquo;s important to also weigh your risk profile against its trade-offs. If your project must be <a href="https://www.w3.org/WAI/standards-guidelines/wcag/">WCAG</a> compliant, disabling autocomplete can break your input for different modalities. For more, see <a href="https://www.w3.org/WAI/WCAG21/Understanding/identify-input-purpose.html">1.3.5: Identify Input Purpose in WCAG 2.1</a>.</p>
<h2 id="keep-errors-generic">Keep errors generic</h2>
<p>While it may seem helpful to let users know whether a piece of data exists, it&rsquo;s also very helpful to attackers. When dealing with accounts, emails, and PII, it&rsquo;s most secure to err (🥁) on the side of less. Instead of returning &ldquo;Your password for this account is incorrect,&rdquo; try the more ambiguous feedback &ldquo;Incorrect login information,&rdquo; and avoid revealing whether the username or email is in the system.</p>
<p>In order to be more helpful, provide a prominent way to contact a human in case an error should arise. Avoid revealing information that isn&rsquo;t necessary. If nothing else, for heaven&rsquo;s sake, don&rsquo;t suggest data that&rsquo;s a close match to the user input.</p>
<h2 id="be-a-bad-guy">Be a bad guy</h2>
<p>When considering security, it&rsquo;s helpful to take a step back, observe the information on display, and ask yourself how a malicious attacker would be able to utilize it. Play devil&rsquo;s advocate. If a bad guy saw this page, what new information would they gain? Does the view show any PII?</p>
<p>Ask yourself if everything on the page is actually necessary for a genuine user. If not, redact or remove it. Less is safer.</p>
<h2 id="security-starts-at-the-front-door">Security starts at the front door</h2>
<p>These days, there&rsquo;s a lot more overlap between coding on the front end and the back end. To create a well-rounded and secure application, it helps to have a general understanding of ways attackers can get their foot in the front door.</p>
]]></content></entry><entry><title type="html">The surprisingly difficult task of printing newlines in a terminal</title><link href="https://victoria.dev/archive/the-surprisingly-difficult-task-of-printing-newlines-in-a-terminal/"/><id>https://victoria.dev/archive/the-surprisingly-difficult-task-of-printing-newlines-in-a-terminal/</id><author><name>Victoria Drake</name></author><published>2019-12-04T09:17:35-05:00</published><updated>2019-12-04T09:17:35-05:00</updated><content type="html"><![CDATA[<p>Surprisingly, getting computers to give humans readable output is no easy feat. With the introduction of <a href="https://en.wikipedia.org/wiki/Standard_streams">standard streams</a> and specifically standard output, programs gained a way to talk to each other using plain text streams; humanizing and displaying stdout is another matter. Technology throughout the computing age has tried to solve this problem, from the use of <a href="https://en.wikipedia.org/wiki/Computer_terminal#Early_VDUs">ASCII characters in video computer displays</a> to modern shell commands like <code>echo</code> and <code>printf</code>.</p>
<p>These advancements have not been seamless. The job of printing output to a terminal is fraught with quirks for programmers to navigate, as exemplified by the deceptively nontrivial task of expanding an <a href="https://en.wikipedia.org/wiki/Escape_sequence">escape sequence</a> to print newlines. The expansion of the placeholder <code>\n</code> can be accomplished in a multitude of ways, each with its own unique history and complications.</p>
<h2 id="using-echo">Using <code>echo</code></h2>
<p>From its appearance in <a href="https://en.wikipedia.org/wiki/Multics">Multics</a> to its modern-day Unix-like system ubiquity, <code>echo</code> remains a familiar tool for getting your terminal to say &ldquo;Hello world!&rdquo; Unfortunately, inconsistent implementations across operating systems make its usage tricky. Where <code>echo</code> on some systems will automatically expand escape sequences, others require the <code>-e</code> option to do the same:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;the study of European nerves is \neurology&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># the study of European nerves is \neurology</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>echo -e <span style="color:#e6db74">&#34;the study of European nerves is \neurology&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># the study of European nerves is</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># eurology</span>
</span></span></code></pre></div><p>Because of these inconsistencies in implementations, <code>echo</code> is considered non-portable. Additionally, its usage in conjunction with user input is relatively easy to corrupt through <a href="https://en.wikipedia.org/wiki/Code_injection#Shell_injection">shell injection attack</a> using command substitutions.</p>
<p>In modern systems, it is retained only to provide compatibility with the many programs that still use it. The <a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html#tag_20_37_16">POSIX specification recommends</a> the use of <code>printf</code> in new programs.</p>
<h2 id="using-printf">Using <code>printf</code></h2>
<p>Since 4th <a href="https://en.wikipedia.org/wiki/Research_Unix#Versions">Edition</a> Unix, the portable <a href="https://en.wikipedia.org/wiki/Printf_(Unix)"><code>printf</code> command</a> has essentially been the new and better <code>echo</code>. It allows you to use <a href="https://en.wikipedia.org/wiki/Printf_format_string#Format_placeholder_specification">format specifiers</a> to humanize input. To interpret backslash escape sequences, use <code>%b</code>. The character sequence <code>\n</code> ensures the output ends with a newline:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>printf <span style="color:#e6db74">&#34;%b\n&#34;</span> <span style="color:#e6db74">&#34;Many females in Oble are \noblewomen&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Many females in Oble are</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># oblewomen</span>
</span></span></code></pre></div><p>Though <code>printf</code> has further options that make it a far more powerful replacement of <code>echo</code>, this utility is not foolproof and can be vulnerable to an <a href="https://en.wikipedia.org/wiki/Uncontrolled_format_string">uncontrolled format string</a> attack. It&rsquo;s important for programmers to ensure they <a href="/blog/sql-injection-and-xss-what-white-hat-hackers-know-about-trusting-user-input/">carefully handle user input</a>.</p>
<h2 id="putting-newlines-in-variables">Putting newlines in variables</h2>
<p>In an effort to improve portability amongst compilers, the <a href="https://en.wikipedia.org/wiki/ANSI_C">ANSI C Standard</a> was established in 1983. With <a href="https://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html#ANSI_002dC-Quoting">ANSI-C quoting</a> using <code>$'...'</code>, <a href="https://en.wikipedia.org/wiki/Escape_sequences_in_C#Table_of_escape_sequences">escape sequences</a> are replaced in output according to the standard.</p>
<p>This allows us to store strings with newlines in variables that are printed with the newlines interpreted. You can do this by setting the variable, then calling it with <code>printf</code> using <code>$</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>puns<span style="color:#f92672">=</span><span style="color:#e6db74">$&#39;\number\narrow\nether\nice&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>printf <span style="color:#e6db74">&#34;%b\n&#34;</span> <span style="color:#e6db74">&#34;These words started with n but don&#39;t make </span>$puns<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># These words started with n but don&#39;t make</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># umber</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># arrow</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># ether</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># ice</span>
</span></span></code></pre></div><p>The expanded variable is single-quoted, which is passed literally to <code>printf</code>. As always, it is important to properly handle the input.</p>
<h2 id="bonus-round-shell-parameter-expansion">Bonus round: shell parameter expansion</h2>
<p>In my article explaining <a href="/posts/bash-and-shell-expansions-lazy-list-making/">Bash and braces</a>, I covered the magic of <a href="https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html">shell parameter expansion</a>. We can use one expansion, <code>${parameter@operator}</code>, to interpret escape sequences, too. We use <code>printf</code>&rsquo;s <code>%s</code> specifier to print as a string, and the <code>E</code> operator will properly expand the escape sequences in our variable:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>printf <span style="color:#e6db74">&#34;%s\n&#34;</span> <span style="color:#e6db74">${</span>puns@E<span style="color:#e6db74">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># umber</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># arrow</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># ether</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># ice</span>
</span></span></code></pre></div><h2 id="the-ongoing-challenge-of-humanizing-output">The ongoing challenge of humanizing output</h2>
<p><a href="https://en.wikipedia.org/wiki/String_interpolation">String interpolation</a> continues to be a chewy problem for programmers. Besides getting languages and shells to agree on what certain placeholders mean, properly using the correct escape sequences requires an eye for detail.</p>
<p>Poor string interpolation can lead to silly-looking output, as well as introduce security vulnerabilities, such as from <a href="https://en.wikipedia.org/wiki/Code_injection">injection attacks</a>. Until the next evolution of the terminal has us talking in emojis, we&rsquo;d best pay attention when printing output for humans.</p>
]]></content></entry><entry><title type="html">The care and feeding of an IoT device</title><link href="https://victoria.dev/archive/the-care-and-feeding-of-an-iot-device/"/><id>https://victoria.dev/archive/the-care-and-feeding-of-an-iot-device/</id><author><name>Victoria Drake</name></author><published>2019-11-27T08:59:35-05:00</published><updated>2019-11-27T08:59:35-05:00</updated><content type="html"><![CDATA[<p>Giving someone a puppy for Christmas might work really well in a movie, but in real life often comes hitched to a multitude of responsibilities that the giftee may not be fully prepared to take on. The same is true for Internet of Things (IoT) devices, including Amazon&rsquo;s Alexa-enabled devices, Google Home, and other Internet-connected appliances like cameras, lightbulbs, and toasters. Yes, they have those now.</p>
<p>Like puppies, IoT devices are still young. Many contain <a href="https://threatpost.com/iot-devices-vulnerable-takeover/144167/">known vulnerabilities</a> that remote attackers can use to gain access to device owners&rsquo; networks. These attacks are sometimes as laughably simple as using a default username and password that the <a href="https://gdpr.report/news/2019/06/12/research-reveals-the-most-vulnerable-iot-devices/">device owner cannot change</a>.</p>
<p>Does all this mean you shouldn&rsquo;t give Grandma Mabel a new app-enabled coffee maker or Ring doorbell for Christmas? Probably, although not necessarily. Like puppies, properly-maintained IoT devices are capable of warming your heart without causing <em>too</em> much havoc; but they take a lot of work to care for. Here are a few responsibilities to keep in mind for the care and feeding of an IoT device.</p>
<h2 id="immature-security">Immature security</h2>
<p>Many manufacturers of IoT devices have not made security a priority. There aren&rsquo;t yet any enforced <a href="https://blog.rapid7.com/2019/03/27/the-iot-cybersecurity-improvement-act-of-2019/">security requirements</a> for this industry, which leaves the protection of your device and the network it&rsquo;s connected to in the hands of the manufacturer.</p>
<p>It&rsquo;s not just obscure no-name toasters, either; malicious third-party apps have snuck onto Amazon&rsquo;s and Google&rsquo;s more reputable devices and enabled attackers to <a href="https://www.cnet.com/news/alexa-and-google-voice-assistants-app-exploits-left-it-vulnerable-to-eavesdropping/">eavesdrop</a> on unsuspecting owners.</p>
<p>Until security regulations are put in place and enforced, it&rsquo;s buyer beware for both devices and third-party applications. To the extent possible, potential owners must do ample research to weed out vulnerable devices and untrustworthy apps.</p>
<h2 id="protecting-your-network">Protecting your network</h2>
<p>If you think hackers aren&rsquo;t likely to find your device in the vast expanse of the Internet, you might be wrong. These days, obscurity doesn&rsquo;t provide security. It&rsquo;s no longer left up to a potential attacker&rsquo;s fallible human eyes to find your insecure front door camera in a cacophony of wireless traffic; <a href="https://money.cnn.com/2013/04/08/technology/security/shodan/index.html">IoT search engines</a> like <a href="https://www.shodan.io/">Shodan</a> will do that for them. Thankfully, these search engines are also used for good, enabling white hat hackers and penetration testers to find and fix insecure devices.</p>
<p>Just like locking your own front door, IoT owners are responsible for locking down access to their devices. This may mean searching through device settings to make sure default credentials are changed, or checking to make sure that a device used on your private home network doesn&rsquo;t by default have public Internet access.</p>
<p>Where the options are available, HTTPS and multifactor authentication should be enabled. The use of a VPN can also keep your devices from being found.</p>
<h2 id="keeping-them-patched">Keeping them patched</h2>
<p>Unlike puppies, many IoT devices are &ldquo;headless&rdquo; and have no inherent way of interfacing with a human. An app-controlled lightbulb, for example, may be all but useless without the software that makes it shine. As convenient as it may be to have your 1500K mood lighting come on automatically at dusk, it also means automatically ceding control of the device to its software developers.</p>
<p>When vulnerabilities in your phone&rsquo;s operating system are discovered and patched, it&rsquo;s likely that automatic updates are pushed and installed overnight, possibly without you even knowing. Your IoT device, on the other hand, may have no such support. In those cases, it&rsquo;s completely up to the user to discover that an update is needed, find and download the patch, then correctly update their device. Even for owners with some technical expertise, this process takes significant effort. Many <a href="https://www.machinedesign.com/industrial-automation/software-updates-are-new-hurdle-iot-security">device owners aren&rsquo;t even aware</a> that their software is dangerously outdated.</p>
<p>In practical terms, this means that users without the time, knowledge, or willingness to keep their devices updated should reconsider owning them. Alternatively, some research can help prospective owners choose devices that receive automatic push updates from their (hopefully responsible) manufacturers over WiFi.</p>
<h2 id="being-responsible">Being responsible</h2>
<p>Raising a healthy and happy IoT device is no small task, especially for potential owners with little time or willingness to put in the required effort. With the proper attention and maintenance, your Internet-connected appliance can bring joy and convenience to your life; but without, it introduces a potential security risk and a whole lot of trouble.</p>
<p>Before getting or giving IoT, be sure the potential owner is up to the task of caring for it.</p>
<p>You can learn more about basic cybersecurity for IoT (as a user or maker) by reading <a href="https://csrc.nist.gov/publications/detail/nistir/8259/draft">NIST&rsquo;s draft guidelines publication</a>.</p>
]]></content></entry><entry><title type="html">Bash and shell expansions: lazy list-making</title><link href="https://victoria.dev/archive/bash-and-shell-expansions-lazy-list-making/"/><id>https://victoria.dev/archive/bash-and-shell-expansions-lazy-list-making/</id><author><name>Victoria Drake</name></author><published>2019-11-18T07:07:24-05:00</published><updated>2019-11-18T07:07:24-05:00</updated><content type="html"><![CDATA[<p>It&rsquo;s that time of year again! When stores start putting up colourful sparkly lit-up plastic bits, we all begin to feel a little festive, and by festive I mean let&rsquo;s go shopping. Specifically, holiday gift shopping! (Gifts for yourself are still gifts, technically.)</p>
<p>Just so this doesn&rsquo;t all go completely madcap, you ought to make some gift lists. Bash can help.</p>
<h2 id="brace-expansion">Brace expansion</h2>
<p>These are not braces: <code>()</code></p>
<p>Neither are these: <code>[]</code></p>
<p><em>These</em> are braces: <code>{}</code></p>
<p>Braces tell Bash to do something with the arbitrary string or strings it finds between them. Multiple strings are comma-separated: <code>{a,b,c}</code>. You can also add an optional preamble and postscript to be attached to each expanded result. Mostly, this can save some typing, such as with common file paths and extensions.</p>
<p>Let&rsquo;s make some lists for each person we want to give stuff to. The following commands are equivalent:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>touch /home/me/gift-lists/Amy.txt /home/me/gift-lists/Bryan.txt /home/me/gift-lists/Charlie.txt
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>touch /home/me/gift-lists/<span style="color:#f92672">{</span>Amy,Bryan,Charlie<span style="color:#f92672">}</span>.txt
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>tree gift-lists
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>/home/me/gift-lists
</span></span><span style="display:flex;"><span>├── Amy.txt
</span></span><span style="display:flex;"><span>├── Bryan.txt
</span></span><span style="display:flex;"><span>└── Charlie.txt
</span></span></code></pre></div><p>Oh darn, &ldquo;Bryan&rdquo; spells his name with an &ldquo;i.&rdquo; I can fix that.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>mv /home/me/gift-lists/<span style="color:#f92672">{</span>Bryan,Brian<span style="color:#f92672">}</span>.txt
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>renamed <span style="color:#e6db74">&#39;/home/me/gift-lists/Bryan.txt&#39;</span> -&gt; <span style="color:#e6db74">&#39;/home/me/gift-lists/Brian.txt&#39;</span>
</span></span></code></pre></div><h2 id="shell-parameter-expansions">Shell parameter expansions</h2>
<p><a href="https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html">Shell parameter expansion</a> allows us to make all sorts of changes to parameters enclosed in braces, like manipulate and substitute text.</p>
<p>There are a few stocking stuffers that all our giftees deserve. Let&rsquo;s make that a variable:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>STUFF<span style="color:#f92672">=</span><span style="color:#e6db74">$&#39;socks\nlump of coal\nwhite chocolate&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;</span>$STUFF<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>socks
</span></span><span style="display:flex;"><span>lump of coal
</span></span><span style="display:flex;"><span>white chocolate
</span></span></code></pre></div><p>Now to add these items to each of our lists with some help from <a href="https://en.wikipedia.org/wiki/Tee_(command)">the <code>tee</code> command</a> to get <code>echo</code> and expansions to play nice.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;</span>$STUFF<span style="color:#e6db74">&#34;</span> | tee <span style="color:#f92672">{</span>Amy,Brian,Charlie<span style="color:#f92672">}</span>.txt
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>cat <span style="color:#f92672">{</span>Amy,Brian,Charlie<span style="color:#f92672">}</span>.txt
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>socks
</span></span><span style="display:flex;"><span>lump of coal
</span></span><span style="display:flex;"><span>white chocolate
</span></span><span style="display:flex;"><span>socks
</span></span><span style="display:flex;"><span>lump of coal
</span></span><span style="display:flex;"><span>white chocolate
</span></span><span style="display:flex;"><span>socks
</span></span><span style="display:flex;"><span>lump of coal
</span></span><span style="display:flex;"><span>white chocolate
</span></span></code></pre></div><h3 id="pattern-match-substitution">Pattern match substitution</h3>
<p>On second thought, maybe the lump of coal isn&rsquo;t such a nice gift. You can replace it with something better using a pattern match substitution in the form of <code>${parameter/pattern/string}</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>STUFF/lump of coal/candy cane<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span> | tee <span style="color:#f92672">{</span>Amy,Brian,Charlie<span style="color:#f92672">}</span>.txt
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>cat <span style="color:#f92672">{</span>Amy,Brian,Charlie<span style="color:#f92672">}</span>.txt
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>socks
</span></span><span style="display:flex;"><span>candy cane
</span></span><span style="display:flex;"><span>white chocolate
</span></span><span style="display:flex;"><span>socks
</span></span><span style="display:flex;"><span>candy cane
</span></span><span style="display:flex;"><span>white chocolate
</span></span><span style="display:flex;"><span>socks
</span></span><span style="display:flex;"><span>candy cane
</span></span><span style="display:flex;"><span>white chocolate
</span></span></code></pre></div><p>This replaces the first instance of &ldquo;lump of coal&rdquo; with &ldquo;candy cane.&rdquo; To replace all instances (if there were multiple), use <code>${parameter//pattern/string}</code>. This doesn&rsquo;t change our <code>$STUFF</code> variable, so we can still reuse the original list for someone naughty later.</p>
<h3 id="substrings">Substrings</h3>
<p>While we&rsquo;re improving things, our giftees may not all like white chocolate. We&rsquo;d better add some regular chocolate to our lists just in case. Since I&rsquo;m super lazy, I&rsquo;m just going to hit the up arrow and modify a previous Bash command. Luckily, the last word in the <code>$STUFF</code> variable is  &ldquo;chocolate,&rdquo; which is nine characters long, so I&rsquo;ll tell Bash to keep just that part using <code>${parameter:offset}</code>. I&rsquo;ll use <code>tee</code>&rsquo;s <code>-a</code> flag to <code>a</code>ppend to my existing lists:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>STUFF: -9<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span> | tee -a <span style="color:#f92672">{</span>Amy,Brian,Charlie<span style="color:#f92672">}</span>.txt
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>cat <span style="color:#f92672">{</span>Amy,Brian,Charlie<span style="color:#f92672">}</span>.txt
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>socks
</span></span><span style="display:flex;"><span>candy cane
</span></span><span style="display:flex;"><span>white chocolate
</span></span><span style="display:flex;"><span>chocolate
</span></span><span style="display:flex;"><span>socks
</span></span><span style="display:flex;"><span>candy cane
</span></span><span style="display:flex;"><span>white chocolate
</span></span><span style="display:flex;"><span>chocolate
</span></span><span style="display:flex;"><span>socks
</span></span><span style="display:flex;"><span>candy cane
</span></span><span style="display:flex;"><span>white chocolate
</span></span><span style="display:flex;"><span>chocolate
</span></span></code></pre></div><p>You can also:</p>
<table>
<thead>
<tr>
<th>Do this</th>
<th>With this</th>
</tr>
</thead>
<tbody>
<tr>
<td>Get substring from <em>n</em> characters onwards</td>
<td><code>${parameter:n}</code></td>
</tr>
<tr>
<td>Get substring for <em>x</em> characters starting at <em>n</em></td>
<td><code>${parameter:n:x}</code></td>
</tr>
</tbody>
</table>
<p>There! Now our base lists are finished. Let&rsquo;s have some eggnog.</p>
<h3 id="testing-variables">Testing variables</h3>
<p>You know, it may be the eggnog, but I think I started a list for Amy yesterday and stored it in a variable that I might have called <code>amy</code>. Let&rsquo;s see if I did. I&rsquo;ll use the <code>${parameter:?word}</code> expansion. It&rsquo;ll write <code>word</code> to standard error and exit if there&rsquo;s no <code>amy</code> parameter.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>amy:?no such<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>bash: amy: no such
</span></span></code></pre></div><p>I guess not. Maybe it was Brian instead?</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>brian:?no such<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Lederhosen
</span></span></code></pre></div><p>You can also:</p>
<table>
<thead>
<tr>
<th>Do this</th>
<th>With this</th>
</tr>
</thead>
<tbody>
<tr>
<td>Substitute <code>word</code> if <code>parameter</code> is unset or null</td>
<td><code>${parameter:-word}</code></td>
</tr>
<tr>
<td>Substitute <code>word</code> if <code>parameter</code> is not unset or null</td>
<td><code>${parameter:+word}</code></td>
</tr>
<tr>
<td>Assign <code>word</code> to <code>parameter</code> if <code>parameter</code> is unset or null</td>
<td><code>${parameter:=word}</code></td>
</tr>
</tbody>
</table>
<h3 id="changing-case">Changing case</h3>
<p>That&rsquo;s right! Brian said he wanted some lederhosen and so I made myself a note. This is pretty important, so I&rsquo;ll add it to Brian&rsquo;s list in capital letters with the <code>${parameter^^pattern}</code> expansion. The <code>pattern</code> part is optional. We&rsquo;re only writing to Brian&rsquo;s list, so I&rsquo;ll just use <code>&gt;&gt;</code> instead of <code>tee -a</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>brian^^<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span> &gt;&gt; Brian.txt
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>cat Brian.txt
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>socks
</span></span><span style="display:flex;"><span>candy cane
</span></span><span style="display:flex;"><span>white chocolate
</span></span><span style="display:flex;"><span>chocolate
</span></span><span style="display:flex;"><span>LEDERHOSEN
</span></span></code></pre></div><p>You can also:</p>
<table>
<thead>
<tr>
<th>Do this</th>
<th>With this</th>
</tr>
</thead>
<tbody>
<tr>
<td>Capitalize the first letter</td>
<td><code>${parameter^pattern}</code></td>
</tr>
<tr>
<td>Lowercase the first letter</td>
<td><code>${parameter,pattern}</code></td>
</tr>
<tr>
<td>Lowercase all letters</td>
<td><code>${parameter,,pattern}</code></td>
</tr>
</tbody>
</table>
<h3 id="expanding-arrays">Expanding arrays</h3>
<p>You know what, all this gift-listing business is a lot of work. I&rsquo;m just going to make <a href="https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Arrays">an array</a> of things I saw at the store:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>gifts<span style="color:#f92672">=(</span>sweater gameboy wagon pillows chestnuts hairbrush<span style="color:#f92672">)</span>
</span></span></code></pre></div><p>I can use substring expansion in the form of <code>${parameter:offset:length}</code> to make this simple. I&rsquo;ll add the first two to Amy&rsquo;s list, the middle two to Brian&rsquo;s, and the last two to Charlie&rsquo;s. I&rsquo;ll  use <code>printf</code> to help with newlines.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>printf <span style="color:#e6db74">&#39;%s\n&#39;</span> <span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>gifts[@]:0:2<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span> &gt;&gt; Amy.txt
</span></span><span style="display:flex;"><span>printf <span style="color:#e6db74">&#39;%s\n&#39;</span> <span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>gifts[@]:2:2<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span> &gt;&gt; Brian.txt
</span></span><span style="display:flex;"><span>printf <span style="color:#e6db74">&#39;%s\n&#39;</span> <span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>gifts[@]: -2<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span> &gt;&gt; Charlie.txt
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>cat Amy.txt
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>socks
</span></span><span style="display:flex;"><span>candy cane
</span></span><span style="display:flex;"><span>white chocolate
</span></span><span style="display:flex;"><span>chocolate
</span></span><span style="display:flex;"><span>sweater
</span></span><span style="display:flex;"><span>gameboy
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>cat Brian.txt
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>socks
</span></span><span style="display:flex;"><span>candy cane
</span></span><span style="display:flex;"><span>white chocolate
</span></span><span style="display:flex;"><span>chocolate
</span></span><span style="display:flex;"><span>LEDERHOSEN
</span></span><span style="display:flex;"><span>wagon
</span></span><span style="display:flex;"><span>pillows
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>cat Charlie.txt
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>socks
</span></span><span style="display:flex;"><span>candy cane
</span></span><span style="display:flex;"><span>white chocolate
</span></span><span style="display:flex;"><span>chocolate
</span></span><span style="display:flex;"><span>chestnuts
</span></span><span style="display:flex;"><span>hairbrush
</span></span></code></pre></div><p>There! Now we&rsquo;ve got a comprehensive set of super personalized gift lists. Thanks Bash! Too bad it can&rsquo;t do the shopping for us, too.</p>
]]></content></entry><entry><title type="html">A cron job that could save you from a ransomware attack</title><link href="https://victoria.dev/archive/a-cron-job-that-could-save-you-from-a-ransomware-attack/"/><id>https://victoria.dev/archive/a-cron-job-that-could-save-you-from-a-ransomware-attack/</id><author><name>Victoria Drake</name></author><published>2019-11-13T08:27:31-04:00</published><updated>2019-11-13T08:27:31-04:00</updated><content type="html"><![CDATA[<p>It&rsquo;s 2019, and ransomware has become a thing.</p>
<p>Systems that interact with the public, like companies, educational institutions, and public services, are most susceptible. While delivery methods for ransomware vary from the physical realm to communication via social sites and email, all methods only require one person to make one mistake in order for ransomware to proliferate.</p>
<p>Ransomware, as you may have heard, is a malicious program that encrypts your files, rendering them unreadable and useless to you. It can include instructions for paying a ransom, usually by sending cryptocurrency, in order to obtain the decryption key. Successful ransomware attacks typically exploit vital, time-sensitive systems. Victims like public services and medical facilities are more likely to have poor or zero recovery processes, leaving governments or insurance providers to reward attackers with ransom payments.</p>
<p>Individuals, especially less-than-tech-savvy ones, are no less at risk. Ransomware can occlude personal documents and family photos that may only exist on one machine.</p>
<p>Thankfully, a fairly low-tech solution exists for rendering ransomware inept: back up your data!</p>
<p>You could achieve this with a straightforward system like plugging in an external hard drive and dragging files over once a day, but this method has a few hurdles. Manually transferring files may be slow or incomplete, and besides, you&rsquo;ll first have to remember to do it.</p>
<p>In my constant pursuit of automating all the things, there&rsquo;s one tool I often return to for its simplicity and reliability: <code>cron</code>. Cron does one thing, and does it well: it runs commands on a schedule.</p>
<p>I first used it a few months shy of three years ago (Have I really been blogging that long?!) to create <a href="/blog/how-i-created-custom-desktop-notifications-using-terminal-and-cron/">custom desktop notifications on Linux</a>. Using the crontab configuration file, which you can edit by running <code>crontab -e</code>, you can specify a schedule for running any commands you like. Here&rsquo;s what the scheduling syntax looks like, from the <a href="https://en.wikipedia.org/wiki/Cron">Wikipedia cron page</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span><span style="color:#75715e"># ┌───────────── minute (0 - 59)</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># │ ┌───────────── hour (0 - 23)</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># │ │ ┌───────────── day of the month (1 - 31)</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># │ │ │ ┌───────────── month (1 - 12)</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># │ │ │ │ ┌───────────── day of the week (0 - 6)</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># │ │ │ │ │</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># │ │ │ │ │</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># │ │ │ │ │</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># * * * * * command to execute</span>
</span></span></code></pre></div><p>For example, a cron job that runs every day at 00:00 would look like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span><span style="color:#ae81ff">0</span> <span style="color:#ae81ff">0</span> * * *
</span></span></code></pre></div><p>To run a job every twelve hours, the syntax is:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span><span style="color:#ae81ff">0</span> */12 * * *
</span></span></code></pre></div><p>This <a href="https://crontab.guru/">great tool</a> can help you wrap your head around the cron scheduling syntax.</p>
<p>What&rsquo;s a scheduler have to do with backing up? By itself, not much. The simple beauty of cron is that it runs commands - any shell commands, and any scripts that you&rsquo;d normally run on the command line. As you may have gleaned from my other posts, I&rsquo;m of the strong opinion that you can do just about anything on the command line, including backing up your files. Options for storage in this area are plentiful, from near-to-free local and cloud options, as well as paid managed services too numerous to list. For CLI tooling, we have utilitarian classics like <code>rsync</code>, and CLI tools for specific cloud providers like AWS.</p>
<h2 id="backing-up-with-rsync">Backing up with <code>rsync</code></h2>
<p><a href="https://en.wikipedia.org/wiki/Rsync">The <code>rsync</code> utility</a> is a classic choice, and can back up your files to an external hard drive or remote server while making intelligent determinations about which files to update. It uses file size and modification times to recognize file changes, and then only transfers changed files, saving time and bandwidth.</p>
<p>The <code>rsync</code> syntax can be a little nuanced; for example, a trailing forward slash will copy just the contents of the directory, instead of the directory itself. I found examples to be helpful in understanding the usage and syntax.</p>
<p>Here&rsquo;s one for backing up a local directory to a local destination, such as an external hard drive:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>rsync -a /home/user/directory /media/user/destination
</span></span></code></pre></div><p>The first argument is the source, and the second is the destination. Reversing these in the above example would copy files from the mounted drive to the local home directory.</p>
<p>The <code>a</code> flag for archive mode is one of <code>rsync</code>&rsquo;s superpowers. Equivalent to flags <code>-rlptgoD</code>, it:</p>
<ul>
<li>Syncs files recursively through directories (<code>r</code>);</li>
<li>Preserves symlinks (<code>l</code>), permissions (<code>p</code>), modification times (<code>t</code>), groups (<code>g</code>), and owner (<code>o</code>); and</li>
<li>Copies device and special files (<code>D</code>).</li>
</ul>
<p>Here&rsquo;s another example, this time for backing up the contents of a local directory to a directory on a remote server using SSH:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>rsync -avze ssh /home/user/directory/ user@remote.host.net:home/user/directory
</span></span></code></pre></div><p>The <code>v</code> flag turns on verbose output, which is helpful if you like realtime feedback on which files are being transferred. During large transfers, however, it can tend to slow things down. The <code>z</code> flag can help with that, as it indicates that files should be compressed during transfer.</p>
<p>The <code>e</code> flag, followed by <code>ssh</code>, tells <code>rsync</code> to use SSH according to the destination instructions provided in the final argument.</p>
<h2 id="backing-up-with-aws-cli">Backing up with AWS CLI</h2>
<p>Amazon Web Services offers a command line interface tool for doing just about everything with your AWS set up, including a straightforward <a href="https://docs.aws.amazon.com/ja_jp/cli/latest/reference/s3/sync.html"><code>s3 sync</code> command</a> for recursively copying new and updated files to your S3 storage buckets. As a storage method for back up data, S3 is a stable and inexpensive choice. You can even <a href="https://docs.aws.amazon.com/AmazonS3/latest/dev/Versioning.html">turn on versioning in your bucket</a>.</p>
<p>The <a href="https://docs.aws.amazon.com/ja_jp/cli/latest/reference/s3/index.html#directory-and-s3-prefix-operations">syntax for interacting with directories</a> is fairly straightforward, and you can directly indicate your S3 bucket as an <code>S3Uri</code> argument in the form of <code>s3://mybucket/mykey</code>. To back up a local directory to your S3 bucket, the command is:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>aws s3 sync /home/user/directory s3://mybucket
</span></span></code></pre></div><p>Similar to <code>rsync</code>, reversing the source and destination would download files from the S3 bucket.</p>
<p>The <code>sync</code> command is intuitive by default. It will guess the mime type of uploaded files, as well as include files discovered by following symlinks. A variety of options exist to control these and other defaults, even including flags to specify the server-side encryption to be used.</p>
<h2 id="setting-up-your-cronjob-back-up">Setting up your cronjob back up</h2>
<p>You can edit your machine&rsquo;s cron file by running:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>crontab -e
</span></span></code></pre></div><p>Intuitive as it may be, it&rsquo;s worth mentioning that your back up commands will only run when your computer is turned on and the cron daemon is running. With this in mind, choose a schedule for your cronjob that aligns with times when your machine is powered on, and maybe not overloaded with other work.</p>
<p>To back up to an S3 bucket every day at 8AM, for example, you&rsquo;d put a line in your crontab that looks like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span><span style="color:#ae81ff">0</span> <span style="color:#ae81ff">8</span> * * * aws s3 sync /home/user/directory s3://mybucket
</span></span></code></pre></div><p>If you&rsquo;re curious whether your cron job is currently running, find the PID of cron with:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>pstree -ap | grep cron
</span></span></code></pre></div><p>Then run <code>pstree -ap &lt;PID&gt;</code>.</p>
<p>This rabbit hole goes deeper; a quick search can reveal different ways of organizing and scheduling cronjobs, or help you find different utilities to run cronjobs when your computer is asleep. To protect against the possibility of ransomware-affected files being transferred to your back up, incrementally separated archives are a good idea. In essence, however, this basic set up is all you really need to create a reliable, automatic back up system.</p>
<h2 id="dont-feed-the-trolls">Don&rsquo;t feed the trolls</h2>
<p>Humans are fallible; that&rsquo;s why cyberattacks work. The success of a ransomware attack depends on the victim having no choice but to pay up in order to return to business as usual. A highly accessible recent back up undermines attackers who depend on us being unprepared. By blowing away a system and restoring from yesterday&rsquo;s back up, we may lose a day of progress; ransomers, however, gain nothing at all.</p>
<p>For further resources on ransomware defense for users and organizations, check out <a href="https://www.us-cert.gov/Ransomware">CISA&rsquo;s advice on ransomware</a>.</p>
]]></content></entry><entry><title type="html">Publishing GitHub event data with GitHub Actions and Pages</title><link href="https://victoria.dev/archive/publishing-github-event-data-with-github-actions-and-pages/"/><id>https://victoria.dev/archive/publishing-github-event-data-with-github-actions-and-pages/</id><author><name>Victoria Drake</name></author><published>2019-11-04T09:13:23-04:00</published><updated>2019-11-04T09:13:23-04:00</updated><content type="html"><![CDATA[<p>Teams who work on GitHub rely on event data to collaborate. The data recorded as issues, pull requests, and comments, become vital to understanding the project.</p>
<p>With the general availability of GitHub Actions, we have a chance to programmatically access and preserve GitHub event data in our repository. Making the data part of the repository itself is a way of preserving it outside of GitHub, and also gives us the ability to feature the data on a front-facing website, such as with GitHub Pages, through an automated process that&rsquo;s part of our CI/CD pipeline.</p>
<p>And, if you&rsquo;re like me, you can turn <a href="https://github.com/victoriadrake/github-guestbook/issues/1">GitHub issue comments</a> into an <a href="https://github.com/victoriadrake/github-guestbook">awesome 90s guestbook page</a>.</p>
<p>No matter the usage, the principle concepts are the same. We can use Actions to access, preserve, and display GitHub event data - with just one workflow file. To illustrate the process, I&rsquo;ll take you through the <a href="https://github.com/victoriadrake/github-guestbook/blob/master/.github/workflows/publish-comments.yml">workflow code</a> that makes my guestbook shine on.</p>
<p>For an introductory look at GitHub Actions including how workflows are triggered, see <a href="/posts/a-lightweight-tool-agnostic-ci/cd-flow-with-github-actions/">A lightweight, tool-agnostic CI/CD flow with GitHub Actions</a>.</p>
<h2 id="accessing-github-event-data">Accessing GitHub event data</h2>
<p>An Action workflow runs in an environment with some default environment variables. A lot of convenient information is available here, including event data. The most complete way to access the event data is using the <code>$GITHUB_EVENT_PATH</code> variable, the path of the file with the complete JSON event payload.</p>
<p>The expanded path looks like <code>/home/runner/work/_temp/_github_workflow/event.json</code> and its data corresponds to its webhook event. You  can find the documentation for webhook event data in GitHub REST API <a href="https://developer.github.com/webhooks/#events">Event Types and Payloads</a>. To make the JSON data available in the workflow environment, you can use a tool like <code>jq</code> to parse the event data and put it in an environment variable.</p>
<p>Below, I grab the comment ID from an <a href="https://developer.github.com/v3/activity/events/types/#issuecommentevent">issue comment event</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>ID<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">$(</span>jq <span style="color:#e6db74">&#39;.comment.id&#39;</span> $GITHUB_EVENT_PATH<span style="color:#66d9ef">)</span><span style="color:#e6db74">&#34;</span>
</span></span></code></pre></div><p>Most event data is also available via the <a href="https://docs.github.com/en/actions/learn-github-actions/contexts#github-context"><code>github.event</code> context variable</a> without needing to parse JSON. The fields are accessed using dot notation, as in the example below where I grab the same comment ID:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>ID<span style="color:#f92672">=</span><span style="color:#e6db74">${</span>{ github.event.comment.id <span style="color:#e6db74">}</span><span style="color:#f92672">}</span>
</span></span></code></pre></div><p>For my guestbook, I want to display entries with the user&rsquo;s handle, and the date and time. I can capture this event data like so:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>AUTHOR<span style="color:#f92672">=</span><span style="color:#e6db74">${</span>{ github.event.comment.user.login <span style="color:#e6db74">}</span><span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span>DATE<span style="color:#f92672">=</span><span style="color:#e6db74">${</span>{ github.event.comment.created_at <span style="color:#e6db74">}</span><span style="color:#f92672">}</span>
</span></span></code></pre></div><p>Shell variables are handy for accessing data, however, they&rsquo;re ephemeral. The workflow environment is created anew each run, and even shell variables set in one step do not persist to other steps. To persist the captured data, you have two options: use artifacts, or commit it to the repository.</p>
<h2 id="preserving-event-data-using-artifacts">Preserving event data: using artifacts</h2>
<p>Using artifacts, you can persist data between workflow jobs without committing it to your repository. This is handy when, for example, you wish to transform or incorporate the data before putting it somewhere more permanent.</p>
<p>Two actions assist with using artifacts: <code>upload-artifact</code> and <code>download-artifact</code>. You can use these actions to make files available to other jobs in the same workflow. For a full example, see <a href="https://docs.github.com/en/actions/advanced-guides/storing-workflow-data-as-artifacts#passing-data-between-jobs-in-a-workflow">passing data between jobs in a workflow</a>.</p>
<p>The <code>upload-artifact</code> action&rsquo;s <code>action.yml</code> contains an <a href="https://github.com/actions/upload-artifact/blob/master/action.yml">explanation</a> of the keywords. The uploaded files are saved in <code>.zip</code> format. Another job in the same workflow run can use the <code>download-artifact</code> action to utilize the data in another step.</p>
<p>You can also manually download the archive on the workflow run page, under the repository&rsquo;s Actions tab.</p>
<p>Persisting workflow data between jobs does not make any changes to the repository files, as the artifacts generated live only in the workflow environment. Personally, being comfortable working in a shell environment, I see a narrow use case for artifacts, though I&rsquo;d have been remiss not to mention them. Besides passing data between jobs, they could be useful for creating <code>.zip</code> format archives of, say, test output data. In the case of my guestbook example, I simply ran all the necessary steps in one job, negating any need for passing data between jobs.</p>
<h2 id="preserving-event-data-pushing-workflow-files-to-the-repository">Preserving event data: pushing workflow files to the repository</h2>
<p>To preserve data captured in the workflow in the repository itself, it is necessary to add and push this data to the Git repository. You can do this in the workflow by creating new files with the data, or by appending data to existing files, using shell commands.</p>
<h3 id="creating-files-in-the-workflow">Creating files in the workflow</h3>
<p>To work with the repository files in the workflow, use the <a href="https://github.com/actions/checkout"><code>checkout</code> action</a> to first get a copy to work with:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yml" data-lang="yml"><span style="display:flex;"><span>- <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/checkout@master</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">with</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">fetch-depth</span>: <span style="color:#ae81ff">1</span>
</span></span></code></pre></div><p>To add comments to my guestbook, I turn the event data captured in shell variables into proper files, using substitutions in <a href="https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html">shell parameter expansion</a> to sanitize user input and translate newlines to paragraphs. I wrote previously about <a href="/blog/sql-injection-and-xss-what-white-hat-hackers-know-about-trusting-user-input/">why user input should be treated carefully</a>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yml" data-lang="yml"><span style="display:flex;"><span>- <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Turn comment into file</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">run</span>: |<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    ID=${{ github.event.comment.id }}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    AUTHOR=${{ github.event.comment.user.login }}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    DATE=${{ github.event.comment.created_at }}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    COMMENT=$(echo &#34;${{ github.event.comment.body }}&#34;)
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    NO_TAGS=${COMMENT//[&lt;&gt;]/\`}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    FOLDER=comments
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    printf &#39;%b\n&#39; &#34;&lt;div class=\&#34;comment\&#34;&gt;&lt;p&gt;${AUTHOR} says:&lt;/p&gt;&lt;p&gt;${NO_TAGS//$&#39;\n&#39;/\&lt;\/p\&gt;\&lt;p\&gt;}&lt;/p&gt;&lt;p&gt;${DATE}&lt;/p&gt;&lt;/div&gt;\r\n&#34; &gt; ${FOLDER}/${ID}.html</span>    
</span></span></code></pre></div><p>By using <code>printf</code> and directing its output with <code>&gt;</code> to a new file, the event data is transformed into an HTML file, named with the comment ID number, that contains the captured event data. Formatted, it looks like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#f92672">div</span> <span style="color:#a6e22e">class</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;comment&#34;</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">p</span>&gt;victoriadrake says:&lt;/<span style="color:#f92672">p</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">p</span>&gt;This is a comment!&lt;/<span style="color:#f92672">p</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">p</span>&gt;2019-11-04T00:28:36Z&lt;/<span style="color:#f92672">p</span>&gt;
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">div</span>&gt;
</span></span></code></pre></div><p>When working with comments, one effect of naming files using the comment ID is that a new file with the same ID will overwrite the previous. This is handy for a guestbook, as it allows any edits to a comment to replace the original comment file.</p>
<p>If you&rsquo;re using a static site generator like Hugo, you could build a Markdown format file, stick it in your <code>content/</code> folder, and the regular site build will take care of the rest. In the case of my simplistic guestbook, I have an extra step to consolidate the individual comment files into a page. Each time it runs, it overwrites the existing <code>index.html</code> with the <code>header.html</code> portion (<code>&gt;</code>), then finds and appends (<code>&gt;&gt;</code>) all the comment files&rsquo; contents in descending order, and lastly appends the <code>footer.html</code> portion to end the page.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yml" data-lang="yml"><span style="display:flex;"><span>- <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Assemble page</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">run</span>: |<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    cat header.html &gt; index.html
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    find comments/ -name &#34;*.html&#34; | sort -r | xargs -I % cat % &gt;&gt; index.html
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    cat footer.html &gt;&gt; index.html</span>    
</span></span></code></pre></div><h3 id="committing-changes-to-the-repository">Committing changes to the repository</h3>
<p>Since the <code>checkout</code> action is not quite the same as cloning the repository, at time of writing, there are some <a href="https://github.community/t5/GitHub-Actions/Checkout-Action-does-not-create-local-master-and-has-no-options/td-p/31575">issues</a> still to work around. A couple extra steps are necessary to <code>pull</code>, <code>checkout</code>, and successfully <code>push</code> changes back to the <code>master</code> branch, but this is pretty trivially done in the shell.</p>
<p>Below is the step that adds, commits, and pushes changes made by the workflow back to the repository&rsquo;s <code>master</code> branch.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yml" data-lang="yml"><span style="display:flex;"><span>- <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Push changes to repo</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">run</span>: |<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    REMOTE=https://${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    git config user.email &#34;${{ github.actor }}@users.noreply.github.com&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    git config user.name &#34;${{ github.actor }}&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    git pull ${REMOTE}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    git checkout master
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    git add .
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    git status
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    git commit -am &#34;Add new comment&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    git push ${REMOTE} master</span>    
</span></span></code></pre></div><p>The remote, in fact, our repository, is specified using the <code>github.repository</code> context variable. For our workflow to be allowed to push to master, we give the remote URL using <a href="https://docs.github.com/en/actions/security-guides/automatic-token-authentication">the default <code>secrets.GITHUB_TOKEN</code> variable</a>.</p>
<p>Since the workflow environment is shiny and newborn, we need to configure Git. In the above example, I&rsquo;ve used the <code>github.actor</code> context variable to input the username of the account initiating the workflow. The email is similarly configured using the <a href="https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-github-user-account/managing-email-preferences/setting-your-commit-email-address#setting-your-commit-email-address-on-github">default <code>noreply</code> GitHub email address</a>.</p>
<h2 id="displaying-event-data">Displaying event data</h2>
<p>If you&rsquo;re using GitHub Pages with the default <code>secrets.GITHUB_TOKEN</code> variable and without a site generator, pushing changes to the repository in the workflow will only update the repository files. The GitHub Pages build will fail with an error, &ldquo;Your site is having problems building: Page build failed.&rdquo;</p>
<p>To enable Actions to trigger a Pages site build, you&rsquo;ll need to create a Personal Access Token. This token can be <a href="https://docs.github.com/en/actions/security-guides/encrypted-secrets#creating-encrypted-secrets-for-a-repository">stored as a secret in the repository</a> settings and passed into the workflow in place of the default <code>secrets.GITHUB_TOKEN</code> variable. I wrote more about <a href="/posts/a-lightweight-tool-agnostic-ci/cd-flow-with-github-actions/#environment-and-variables">Actions environment and variables in this post</a>.</p>
<p>With the use of a Personal Access Token, a push initiated by the Actions workflow will also update the Pages site. You can see it for yourself by <a href="https://github.com/victoriadrake/github-guestbook/issues/1">leaving a comment</a> in my guestbook! The comment creation event triggers the workflow, which then takes around 30 seconds to run and update the guestbook page.</p>
<p>Where a site build is necessary for changes to be published, such as when using Hugo, an Action can do this too. However, in order to avoid creating unintended loops, one Action workflow will not trigger another (<a href="https://docs.github.com/en/actions/using-workflows/triggering-a-workflow">see what will</a>). Instead, it&rsquo;s extremely convenient to handle the process of <a href="/posts/a-portable-makefile-for-continuous-delivery-with-hugo-and-github-pages/">building the site with a Makefile</a>, which any workflow can then run. Simply add running the Makefile as the final step in your workflow job, with the repository token where necessary:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yml" data-lang="yml"><span style="display:flex;"><span>- <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Run Makefile</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">env</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">TOKEN</span>: <span style="color:#ae81ff">${{ secrets.GITHUB_TOKEN }}</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">run</span>: <span style="color:#ae81ff">make all</span>
</span></span></code></pre></div><p>This ensures that the final step of your workflow builds and deploys the updated site.</p>
<h2 id="no-more-event-data-horizon">No more event data horizon</h2>
<p>GitHub Actions provides a neat way to capture and utilize event data so that it&rsquo;s not only available within GitHub. The possibilities are only as limited as your imagination! Here are a few ideas for things this lets us create:</p>
<ol>
<li>A public-facing issues board, where customers without GitHub accounts can view and give feedback on project issues.</li>
<li>An automatically-updating RSS feed of new issues, comments, or PRs for any repository.</li>
<li>A comments system for static sites, utilizing GitHub issue comments as an input method.</li>
<li>An awesome 90s guestbook page.</li>
</ol>
<p>Did I mention I made a 90s guestbook page? My inner-Geocities-nerd is a little excited.</p>
]]></content></entry><entry><title type="html">A lightweight, tool-agnostic CI/CD flow with GitHub Actions</title><link href="https://victoria.dev/archive/a-lightweight-tool-agnostic-ci/cd-flow-with-github-actions/"/><id>https://victoria.dev/archive/a-lightweight-tool-agnostic-ci/cd-flow-with-github-actions/</id><author><name>Victoria Drake</name></author><published>2019-10-28T08:28:52-04:00</published><updated>2019-10-28T08:28:52-04:00</updated><content type="html"><![CDATA[<p>Agnostic tooling is the clever notion that you should be able to run your code in various environments. With many continuous integration and continuous development (CI/CD) apps available, agnostic tooling gives developers a big advantage: portability.</p>
<p>Of course, having your CI/CD work <em>everywhere</em> is a tall order. Popular <a href="https://github.com/marketplace/category/continuous-integration">CI apps for GitHub repositories</a> alone use a multitude of configuration languages spanning <a href="https://groovy-lang.org/syntax.html">Groovy</a>, <a href="https://yaml.org/">YAML</a>, <a href="https://github.com/toml-lang/toml">TOML</a>, <a href="https://json.org/">JSON</a>, and more&hellip; all with differing syntax, of course. Porting workflows from one tool to another is more than a one-cup-of-coffee process.</p>
<p>The introduction of <a href="https://github.com/features/actions">GitHub Actions</a> has the potential to add yet another tool to the mix; or, for the right set up, greatly simplify a CI/CD workflow.</p>
<p>Prior to this article, I accomplished my CD flow with several lashed-together apps. I used AWS Lambda to trigger site builds on a schedule. I had Netlify build on push triggers, as well as run image optimization, and then push my site to the public Pages repository. I used Travis CI in the public repository to test the HTML. All this worked in conjunction with GitHub Pages, which actually hosts the site.</p>
<p>I&rsquo;m now using the GitHub Actions beta to accomplish all the same tasks, with one <a href="/posts/a-portable-makefile-for-continuous-delivery-with-hugo-and-github-pages/">portable Makefile</a> of build instructions, and without any other CI/CD apps.</p>
<h2 id="appreciating-the-shell">Appreciating the shell</h2>
<p>What do most CI/CD tools have in common? They run your workflow instructions in a shell environment! This is wonderful, because that means that most CI/CD tools can do anything that you can do in a terminal&hellip; and you can do pretty much <em>anything</em> in a terminal.</p>
<p>Especially for a contained use case like building my static site with a generator like Hugo, running it all in a shell is a no-brainer. To tell the magic box what to do, we just need to write instructions.</p>
<p>While a shell script is certainly the most portable option, I use the still-very-portable <a href="https://en.wikipedia.org/wiki/Make_(software)">Make</a> to write my process instructions. This provides me with some advantages over simple shell scripting, like the use of variables and <a href="https://en.wikipedia.org/wiki/Make_(software)#Macros">macros</a>, and the modularity of <a href="https://en.wikipedia.org/wiki/Makefile#Rules">rules</a>.</p>
<p>I got into the <a href="/posts/a-portable-makefile-for-continuous-delivery-with-hugo-and-github-pages/">nitty-gritty of my Makefile in my last post</a>. Let&rsquo;s look at how to get GitHub Actions to run it.</p>
<h2 id="using-a-makefile-with-github-actions">Using a Makefile with GitHub Actions</h2>
<p>To our point on portability, my magic Makefile is stored right in the repository root. Since it&rsquo;s included with the code, I can run the Makefile locally on any system where I can clone the repository, provided I set the environment variables. Using GitHub Actions as my CI/CD tool is as straightforward as making Make go worky-worky.</p>
<p>I found the <a href="https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions">GitHub Actions workflow syntax guide</a> to be pretty straightforward, though also lengthy on options. Here&rsquo;s the necessary set up for getting the Makefile to run.</p>
<p>The workflow file at <code>.github/workflows/make-master.yml</code> contains the following:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yml" data-lang="yml"><span style="display:flex;"><span><span style="color:#f92672">name</span>: <span style="color:#ae81ff">make-master</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">on</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">push</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">branches</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#ae81ff">master</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">schedule</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">cron</span>: <span style="color:#e6db74">&#39;20 13 * * *&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">jobs</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">build</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">runs-on</span>: <span style="color:#ae81ff">ubuntu-latest</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">steps</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/checkout@master</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">with</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">fetch-depth</span>: <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Run Makefile</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">env</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">TOKEN</span>: <span style="color:#ae81ff">${{ secrets.TOKEN }}</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">run</span>: <span style="color:#ae81ff">make all</span>
</span></span></code></pre></div><p>I&rsquo;ll explain the components that make this work.</p>
<h2 id="triggering-the-workflow">Triggering the workflow</h2>
<p>Actions support multiple <a href="https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows">triggers for a workflow</a>. Using the <code>on</code> syntax, I&rsquo;ve defined two triggers for mine: a <a href="https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#onpushbranchestagsbranches-ignoretags-ignore">push event</a> to the <code>master</code> branch only, and a <a href="https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule">scheduled</a> <code>cron</code> job.</p>
<p>Once the <code>make-master.yml</code> file is in your repository, either of your triggers will cause Actions to run your Makefile. To see how the last run went, you can also <a href="https://docs.github.com/en/actions/monitoring-and-troubleshooting-workflows/adding-a-workflow-status-badge">add a fun badge</a> to the README.</p>
<h3 id="one-hacky-thing">One hacky thing</h3>
<p>Because the Makefile runs on every push to <code>master</code>, I sometimes would get errors when the site build had no changes. When Git, via <a href="/posts/a-portable-makefile-for-continuous-delivery-with-hugo-and-github-pages/">my Makefile</a>, attempted to commit to the Pages repository, no changes were detected and the commit would fail annoyingly:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>nothing to commit, working tree clean
</span></span><span style="display:flex;"><span>On branch master
</span></span><span style="display:flex;"><span>Your branch is up to date with &#39;origin/master&#39;.
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>nothing to commit, working tree clean
</span></span><span style="display:flex;"><span>Makefile:62: recipe for target &#39;deploy&#39; failed
</span></span><span style="display:flex;"><span>make: *** [deploy] Error 1
</span></span><span style="display:flex;"><span>##[error]Process completed with exit code 2.
</span></span></code></pre></div><p>I came across some solutions that proposed using <code>diff</code> to check if a commit should be made, but this may not work for <a href="https://github.com/benmatselby/hugo-deploy-gh-pages/issues/4">reasons</a>. As a workaround, I simply added the <a href="https://gohugo.io/methods/time/utc/">current UTC time</a> to my index page so that every build would contain a change to be committed.</p>
<h2 id="environment-and-variables">Environment and variables</h2>
<p>You can define the <a href="https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources">virtual environment</a> for your workflow to run in using the <code>runs-on</code> syntax. The <del>obvious best choice</del> one I chose is Ubuntu. Using <code>ubuntu-latest</code> gets me the most updated version, whatever that happens to be when you&rsquo;re reading this.</p>
<p>GitHub sets some <a href="https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables">default environment variables</a> for workflows. The <a href="https://github.com/actions/checkout"><code>actions/checkout</code> action</a> with <code>fetch-depth: 1</code> creates a copy of just the most recent commit your repository in the <code>GITHUB_WORKSPACE</code> variable. This allows the workflow to access the Makefile at <code>GITHUB_WORKSPACE/Makefile</code>. Without using the checkout action, the Makefile won&rsquo;t be found, and I get an error that looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>make: *** No rule to make target &#39;all&#39;.  Stop.
</span></span><span style="display:flex;"><span>Running Makefile
</span></span><span style="display:flex;"><span>##[error]Process completed with exit code 2.
</span></span></code></pre></div><p>While there is a <a href="https://docs.github.com/en/actions/security-guides/automatic-token-authentication">default <code>GITHUB_TOKEN</code> secret</a>, this is not the one I used. The default is only locally scoped to the current repository. To be able to push to my separate GitHub Pages repository, I created a <a href="https://github.com/settings/tokens">personal access token</a> scoped to <code>public_repo</code> and pass it in as the <code>secrets.TOKEN</code> encrypted variable. For a step-by-step, see <a href="https://docs.github.com/en/actions/security-guides/encrypted-secrets">Creating and using encrypted secrets</a>.</p>
<h2 id="portable-tooling">Portable tooling</h2>
<p>The nice thing about using a simple Makefile to define the bulk of my CI/CD process is that it&rsquo;s completely portable. I can run a Makefile anywhere I have access to an environment, which is most CI/CD apps, virtual instances, and, of course, on my local machine.</p>
<p>One of the reasons I like GitHub Actions is that getting my Makefile to run was pretty straightforward. I think the syntax is well done - easy to read, and intuitive when it comes to finding an option you&rsquo;re looking for. For someone already using GitHub Pages, Actions provides a pretty seamless CD experience; and if that should ever change, I can run my Makefile elsewhere. ¯\_(ツ)_/¯</p>
]]></content></entry><entry><title type="html">A portable Makefile for continuous delivery with Hugo and GitHub Pages</title><link href="https://victoria.dev/archive/a-portable-makefile-for-continuous-delivery-with-hugo-and-github-pages/"/><id>https://victoria.dev/archive/a-portable-makefile-for-continuous-delivery-with-hugo-and-github-pages/</id><author><name>Victoria Drake</name></author><published>2019-10-21T09:09:06-04:00</published><updated>2019-10-21T09:09:06-04:00</updated><content type="html"><![CDATA[<p>Fun fact: I first launched this GitHub Pages site 1,018 days ago.</p>
<p>Since then, we&rsquo;ve grown together. From early cringe-worthy commit messages, through eighty-six versions of <a href="https://gohugo.io/">Hugo</a>, and up until last week, a less-than-streamlined multi-app continuous integration and deployment (CI/CD) workflow.</p>
<p>If you know me at all, you know I love to automate things. I&rsquo;ve been using a combination of AWS Lambda, Netlify, and Travis CI to automatically build and publish this site. My workflow for the task includes:</p>
<ul>
<li>Build with <a href="https://gohugo.io/">Hugo</a> on push to master, and on a schedule (Netlify and Lambda);</li>
<li>Optimize and resize images (Netlify);</li>
<li>Test with <a href="https://github.com/gjtorikian/html-proofer">HTMLProofer</a> (Travis CI); and</li>
<li>Deploy to my <a href="/blog/two-ways-to-deploy-a-public-github-pages-site-from-a-private-hugo-repository/">separate, public, GitHub Pages repository</a> (Netlify).</li>
</ul>
<p>Thanks to the introduction of GitHub Actions, I&rsquo;m able to do all the above with just one portable <a href="https://en.wikipedia.org/wiki/Makefile">Makefile</a>.</p>
<p>Next week I&rsquo;ll cover my Actions set up; today, I&rsquo;ll take you through the nitty-gritty of my Makefile so you can write your own.</p>
<h2 id="makefile-portability">Makefile portability</h2>
<p><a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/make.html">POSIX-standard-flavour Make</a> runs on every Unix-like system out there. <a href="https://en.wikipedia.org/wiki/Make_(software)#Derivatives">Make derivatives</a>, such as <a href="https://www.gnu.org/software/make/">GNU Make</a> and several flavours of BSD Make also run on Unix-like systems, though their particular use requires installing the respective program. To write a truly portable Makefile, mine follows the POSIX standard. (For a more thorough summation of POSIX-compatible Makefiles, I found this article helpful: <a href="https://nullprogram.com/blog/2017/08/20/">A Tutorial on Portable Makefiles</a>.) I run Ubuntu, so I&rsquo;ve tested the portability aspect using the BSD Make programs <code>bmake</code>, <code>pmake</code>, and <code>fmake</code>. Compatibility with non-Unix-like systems is a little more complicated, since shell commands differ. With derivatives such as Nmake, it&rsquo;s better to write a separate Makefile with appropriate Windows commands.</p>
<p>While much of my particular use case could be achieved with shell scripting, I find Make offers some worthwhile advantages. I enjoy the ease of using variables and <a href="https://en.wikipedia.org/wiki/Make_(software)#Macros">macros</a>, and the modularity of <a href="https://en.wikipedia.org/wiki/Makefile#Rules">rules</a> when it comes to organizing my steps.</p>
<p>The writing of rules mostly comes down to shell commands, which is the main reason Makefiles are as portable as they are. The best part is that you can do pretty much <em>anything</em> in a terminal, and certainly handle all the workflow steps listed above.</p>
<h2 id="my-continuous-deployment-makefile">My continuous deployment Makefile</h2>
<p>Here&rsquo;s the portable Makefile that handles my workflow. Yes, I put emojis in there. I&rsquo;m a monster.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-Makefile" data-lang="Makefile"><span style="display:flex;"><span><span style="color:#a6e22e">.POSIX</span><span style="color:#f92672">:</span>
</span></span><span style="display:flex;"><span>DESTDIR<span style="color:#f92672">=</span>public
</span></span><span style="display:flex;"><span>HUGO_VERSION<span style="color:#f92672">=</span>0.58.3
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>OPTIMIZE <span style="color:#f92672">=</span> find <span style="color:#66d9ef">$(</span>DESTDIR<span style="color:#66d9ef">)</span> -not -path <span style="color:#e6db74">&#34;*/static/*&#34;</span> <span style="color:#ae81ff">\(</span> -name <span style="color:#e6db74">&#39;*.png&#39;</span> -o -name <span style="color:#e6db74">&#39;*.jpg&#39;</span> -o -name <span style="color:#e6db74">&#39;*.jpeg&#39;</span> <span style="color:#ae81ff">\)</span> -print0 | <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span><span style="color:#960050;background-color:#1e0010">xargs</span> <span style="color:#960050;background-color:#1e0010">-0</span> <span style="color:#960050;background-color:#1e0010">-P8</span> <span style="color:#960050;background-color:#1e0010">-n2</span> <span style="color:#960050;background-color:#1e0010">mogrify</span> <span style="color:#960050;background-color:#1e0010">-strip</span> <span style="color:#960050;background-color:#1e0010">-thumbnail</span> <span style="color:#e6db74">&#39;1000&gt;&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">.PHONY</span><span style="color:#f92672">:</span> all
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">all</span><span style="color:#f92672">:</span> get_repository clean get build test deploy
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">.PHONY</span><span style="color:#f92672">:</span> get_repository
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">get_repository</span><span style="color:#f92672">:</span>
</span></span><span style="display:flex;"><span> @echo <span style="color:#e6db74">&#34;🛎 Getting Pages repository&#34;</span>
</span></span><span style="display:flex;"><span> git clone https://github.com/victoriadrake/victoriadrake.github.io.git <span style="color:#66d9ef">$(</span>DESTDIR<span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">.PHONY</span><span style="color:#f92672">:</span> clean
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">clean</span><span style="color:#f92672">:</span>
</span></span><span style="display:flex;"><span> @echo <span style="color:#e6db74">&#34;🧹 Cleaning old build&#34;</span>
</span></span><span style="display:flex;"><span> cd <span style="color:#66d9ef">$(</span>DESTDIR<span style="color:#66d9ef">)</span> <span style="color:#f92672">&amp;&amp;</span> rm -rf *
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">.PHONY</span><span style="color:#f92672">:</span> get
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">get</span><span style="color:#f92672">:</span>
</span></span><span style="display:flex;"><span> @echo <span style="color:#e6db74">&#34;❓ Checking for hugo&#34;</span>
</span></span><span style="display:flex;"><span> @if ! <span style="color:#f92672">[</span> -x <span style="color:#e6db74">&#34;</span>$$<span style="color:#e6db74">(command -v hugo)&#34;</span> <span style="color:#f92672">]</span>; <span style="color:#66d9ef">then</span><span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  echo <span style="color:#e6db74">&#34;🤵 Getting Hugo&#34;</span>;<span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>     wget -q -P tmp/ https://github.com/gohugoio/hugo/releases/download/v<span style="color:#66d9ef">$(</span>HUGO_VERSION<span style="color:#66d9ef">)</span>/hugo_extended_<span style="color:#66d9ef">$(</span>HUGO_VERSION<span style="color:#66d9ef">)</span>_Linux-64bit.tar.gz;<span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  tar xf tmp/hugo_extended_<span style="color:#66d9ef">$(</span>HUGO_VERSION<span style="color:#66d9ef">)</span>_Linux-64bit.tar.gz -C tmp/;<span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  sudo mv -f tmp/hugo /usr/bin/;<span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  rm -rf tmp/;<span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  hugo version;<span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span> <span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">.PHONY</span><span style="color:#f92672">:</span> build
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">build</span><span style="color:#f92672">:</span>
</span></span><span style="display:flex;"><span> @echo <span style="color:#e6db74">&#34;🍳 Generating site&#34;</span>
</span></span><span style="display:flex;"><span> hugo --gc --minify -d <span style="color:#66d9ef">$(</span>DESTDIR<span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> @echo <span style="color:#e6db74">&#34;🧂 Optimizing images&#34;</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">$(</span>OPTIMIZE<span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">.PHONY</span><span style="color:#f92672">:</span> test
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">test</span><span style="color:#f92672">:</span>
</span></span><span style="display:flex;"><span> @echo <span style="color:#e6db74">&#34;🍜 Testing HTML&#34;</span>
</span></span><span style="display:flex;"><span> docker run -v <span style="color:#66d9ef">$(</span>GITHUB_WORKSPACE<span style="color:#66d9ef">)</span>/<span style="color:#66d9ef">$(</span>DESTDIR<span style="color:#66d9ef">)</span>/:/mnt 18fgsa/html-proofer mnt --disable-external
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">.PHONY</span><span style="color:#f92672">:</span> deploy
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">deploy</span><span style="color:#f92672">:</span>
</span></span><span style="display:flex;"><span> @echo <span style="color:#e6db74">&#34;🎁 Preparing commit&#34;</span>
</span></span><span style="display:flex;"><span> @cd <span style="color:#66d9ef">$(</span>DESTDIR<span style="color:#66d9ef">)</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span> <span style="color:#f92672">&amp;&amp;</span> git config user.email <span style="color:#e6db74">&#34;hello@victoria.dev&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span> <span style="color:#f92672">&amp;&amp;</span> git config user.name <span style="color:#e6db74">&#34;Victoria via GitHub Actions&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span> <span style="color:#f92672">&amp;&amp;</span> git add . <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span> <span style="color:#f92672">&amp;&amp;</span> git status <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span> <span style="color:#f92672">&amp;&amp;</span> git commit -m <span style="color:#e6db74">&#34;🤖 CD bot is helping&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span> <span style="color:#f92672">&amp;&amp;</span> git push -f -q https://<span style="color:#66d9ef">$(</span>TOKEN<span style="color:#66d9ef">)</span>@github.com/victoriadrake/victoriadrake.github.io.git master
</span></span><span style="display:flex;"><span> @echo <span style="color:#e6db74">&#34;🚀 Site is deployed!&#34;</span>
</span></span></code></pre></div><p>Sequentially, this workflow:</p>
<ol>
<li>Clones the public Pages repository;</li>
<li>Cleans (deletes) the previous build files;</li>
<li>Downloads and installs the specified version of Hugo, if Hugo is not already present;</li>
<li>Builds the site;</li>
<li>Optimizes images;</li>
<li>Tests the built site with HTMLProofer, and</li>
<li>Prepares a new commit and pushes to the public Pages repository.</li>
</ol>
<p>If you&rsquo;re familiar with command line, most of this may look familiar. Here are a couple bits that might warrant a little explanation.</p>
<h3 id="checking-if-a-program-is-already-installed">Checking if a program is already installed</h3>
<p>I think this bit is pretty tidy:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span><span style="color:#66d9ef">if</span> ! <span style="color:#f92672">[</span> -x <span style="color:#e6db74">&#34;</span>$$<span style="color:#e6db74">(command -v hugo)&#34;</span> <span style="color:#f92672">]</span>; <span style="color:#66d9ef">then</span><span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>...
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fi</span>
</span></span></code></pre></div><p>I use a negated <code>if</code> conditional in conjunction with <code>command -v</code> to check if an executable (<code>-x</code>) called <code>hugo</code> exists. If one is not present, the script gets the specified version of Hugo and installs it. <a href="https://stackoverflow.com/a/677212">This Stack Overflow answer</a> has a nice summation of why <code>command -v</code> is a more portable choice than <code>which</code>.</p>
<h3 id="image-optimization">Image optimization</h3>
<p>My Makefile uses <code>mogrify</code> to batch resize and compress images in particular folders. It finds them automatically using the file extension, and only modifies images that are larger than the target size of 1000px in any dimension. I wrote more about the <a href="/blog/how-to-quickly-batch-resize-compress-and-convert-images-with-a-bash-one-liner/">batch-processing one-liner in this post</a>.</p>
<p>There are a few different ways to achieve this same task, one of which, theoretically, is to take advantage of Make&rsquo;s <a href="https://en.wikipedia.org/wiki/Make_(software)#Suffix_rules">suffix rules</a> to run commands only on image files. I find the shell script to be more readable.</p>
<h3 id="using-dockerized-htmlproofer">Using Dockerized HTMLProofer</h3>
<p>HTMLProofer is installed with <code>gem</code>, and uses Ruby and <a href="https://nokogiri.org/tutorials/ensuring_well_formed_markup.html">Nokogiri</a>, which adds up to a lot of installation time for a CI workflow. Thankfully, <a href="https://github.com/18F">18F</a> has a <a href="https://github.com/18F/html-proofer-docker">Dockerized version</a> that is much faster to implement. Its usage requires starting the container with the built site directory <a href="https://docs.docker.com/storage/volumes/#start-a-container-with-a-volume">mounted as a data volume</a>, which is easily achieved by appending to the <code>docker run</code> command.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>docker run -v /absolute/path/to/site/:/mounted-site 18fgsa/html-proofer /mounted-site
</span></span></code></pre></div><p>In my Makefile, I specify the absolute site path using the <a href="https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables">default environment variable</a> <code>GITHUB_WORKSPACE</code>. I&rsquo;ll dive into this and other GitHub Actions features in the next post.</p>
<p>In the meantime, happy Making!</p>
]]></content></entry><entry><title type="html">How to quickly batch resize, compress, and convert images with a Bash one-liner</title><link href="https://victoria.dev/archive/how-to-quickly-batch-resize-compress-and-convert-images-with-a-bash-one-liner/"/><id>https://victoria.dev/archive/how-to-quickly-batch-resize-compress-and-convert-images-with-a-bash-one-liner/</id><author><name>Victoria Drake</name></author><published>2019-10-14T08:27:49-04:00</published><updated>2019-10-14T08:27:49-04:00</updated><content type="html"><![CDATA[<p>Part of my Hugo site continuous deployment workflow is the processing of 210 images, at time of writing.</p>
<p>Here&rsquo;s my one-liner:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>find public/ -not -path <span style="color:#e6db74">&#34;*/static/*&#34;</span> <span style="color:#ae81ff">\(</span> -name <span style="color:#e6db74">&#39;*.png&#39;</span> -o -name <span style="color:#e6db74">&#39;*.jpg&#39;</span> -o -name <span style="color:#e6db74">&#39;*.jpeg&#39;</span> <span style="color:#ae81ff">\)</span> -print0 | xargs -0 -P8 -n2 mogrify -strip -thumbnail <span style="color:#e6db74">&#39;1000&gt;&#39;</span> -format jpg
</span></span></code></pre></div><p>I use <code>find</code> to target only certain image file formats in certain directories. With <a href="https://www.imagemagick.org/script/mogrify.php"><code>mogrify</code>, part of ImageMagick</a>, I resize only the images that are larger than a certain dimension, compress them, and strip the metadata. I tack on the <code>format</code> flag to create jpg copies of the images.</p>
<p>Here&rsquo;s the one-liner again (broken up for better reading):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span><span style="color:#75715e"># Look in the public/ directory</span>
</span></span><span style="display:flex;"><span>find public/ <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span><span style="color:#75715e"># Ignore directories called &#34;static&#34; regardless of location</span>
</span></span><span style="display:flex;"><span>-not -path <span style="color:#e6db74">&#34;*/static/*&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span><span style="color:#75715e"># Print the file paths of all files ending with any of these extensions</span>
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">\(</span> -name <span style="color:#e6db74">&#39;*.png&#39;</span> -o -name <span style="color:#e6db74">&#39;*.jpg&#39;</span> -o -name <span style="color:#e6db74">&#39;*.jpeg&#39;</span> <span style="color:#ae81ff">\)</span> -print0 <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span><span style="color:#75715e"># Pipe the file paths to xargs and use 8 parallel workers to process 2 arguments</span>
</span></span><span style="display:flex;"><span>| xargs -0 -P8 -n2 <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span><span style="color:#75715e"># Tell mogrify to strip metadata, and...</span>
</span></span><span style="display:flex;"><span>mogrify -strip <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span><span style="color:#75715e"># ...compress and resize any images larger than the target size (1000px in either dimension)</span>
</span></span><span style="display:flex;"><span>-thumbnail <span style="color:#e6db74">&#39;1000&gt;&#39;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span><span style="color:#75715e"># Convert the files to jpg format</span>
</span></span><span style="display:flex;"><span>-format jpg
</span></span></code></pre></div><p>That&rsquo;s it. That&rsquo;s the post.</p>
]]></content></entry><entry><title type="html">How Engineering Leaders Build Security Culture Through Architecture Decisions</title><link href="https://victoria.dev/posts/how-engineering-leaders-build-security-culture-through-architecture-decisions/"/><id>https://victoria.dev/posts/how-engineering-leaders-build-security-culture-through-architecture-decisions/</id><author><name>Victoria Drake</name></author><published>2019-09-30T08:03:12-04:00</published><updated>2019-09-30T08:03:12-04:00</updated><content type="html"><![CDATA[<p>Leading engineering teams means constantly balancing several goals: speed to market, feature development, technical debt, and security. When I&rsquo;ve seen teams struggle with security, it&rsquo;s rarely because they lack technical knowledge. The real challenge is creating an organizational culture where security decisions are prioritized, even under pressure.</p>
<p>The &ldquo;we can do security later&rdquo; mindset creates what I call security debt—technical decisions that make it exponentially harder to secure applications as they scale. Unlike other forms of technical debt, security debt compounds in immediately dangerous ways. A rushed architectural decision made to meet a deadline can become a persistent vulnerability that affects every feature built on top of it.</p>
<p>As engineering leaders, we have a unique opportunity to shape how our teams think about security. The architectural decisions we make and the frameworks we establish don&rsquo;t just affect our current codebase—they define the security culture that will carry our teams through future challenges.</p>
<blockquote>
<p>I&rsquo;ve found that the most effective approach isn&rsquo;t to mandate security practices after the fact, but to build security thinking into the fundamental architectural decisions that guide daily development work.</p>
</blockquote>
<p>When teams understand why certain architectural patterns prevent entire classes of vulnerabilities, they start making secure choices naturally.</p>
<h2 id="the-leadership-challenge-building-security-culture-through-architecture">The Leadership Challenge: Building Security Culture Through Architecture</h2>
<p>The difference between teams that build secure applications and those that struggle with security incidents comes down to how engineering leaders approach architectural decision-making. Security-conscious teams don&rsquo;t just follow security checklists—they&rsquo;ve internalized security principles that guide their architectural choices.</p>
<p>This cultural shift happens when engineering leaders consistently demonstrate how security considerations influence technical decisions. When your team sees you weighing security implications during architecture reviews, evaluating third-party libraries through a security lens, and making trade-offs that prioritize long-term security over short-term convenience, they learn to apply the same thinking to their own work.</p>
<p>The framework I&rsquo;ve developed focuses on three architectural principles that, when consistently applied, create a foundation for security-conscious engineering culture:</p>
<ol>
<li><strong>Strategic Separation</strong>: Designing systems that isolate different types of data and functionality</li>
<li><strong>Intentional Configuration</strong>: Making deliberate choices about system defaults and access patterns</li>
<li><strong>Controlled Access</strong>: Building authorization thinking into system design from the start</li>
</ol>
<p>These are are both technical guidelines and leadership tools for building teams that make decisions that promote built-in security by default.</p>
<h2 id="strategic-separation-teaching-teams-to-think-in-security-boundaries">Strategic Separation: Teaching Teams to Think in Security Boundaries</h2>
<p>The most effective security-conscious engineering teams I&rsquo;ve worked with share a common trait: they instinctively think in terms of security boundaries. This isn&rsquo;t something that happens overnight—it&rsquo;s a cultural shift that engineering leaders must deliberately cultivate through architectural decisions and team education.</p>
<p>When I talk about strategic separation, I mean designing systems that isolate different types of data and functionality based on their security requirements and organizational impact. The goal isn&rsquo;t just to prevent specific vulnerabilities, but to create architectural patterns that make it obvious to your team when they&rsquo;re crossing security boundaries.</p>
<p>Consider a common scenario that exposes how teams think about security:</p>
<p>Your team is building a user profile feature that includes photo uploads. The natural instinct is to store user photos alongside other application assets. After all, they&rsquo;re both images. But this decision reveals whether your team thinks in terms of security boundaries.</p>
<p>A security-conscious team immediately recognizes that user-uploaded content and application assets have fundamentally different security requirements. Application assets are controlled, vetted, and part of your deployment process. User uploads are untrusted input that could contain malicious content or exploit path traversal vulnerabilities to access sensitive configuration files.</p>
<p>The architectural decision here can prevent path traversal attacks, but it also establishes a pattern that helps your team understand the security implications of data boundary decisions.</p>
<blockquote>
<p>When you consistently demonstrate that different types of data require different security approaches, your team starts applying this thinking to database design, API endpoints, and service architecture.</p>
</blockquote>
<p>This is where engineering leadership becomes crucial. The technical solution is straightforward: separate user-uploaded content from application assets using different storage systems, domains, or security contexts. But the leadership challenge is helping your team understand why this separation matters and how to apply the same thinking to future architectural decisions.</p>
<p>I&rsquo;ve found that the most effective approach is to make security boundaries visible in your architecture discussions. When reviewing designs, ask questions like: &ldquo;What happens if this data is compromised or malicious?” and &ldquo;How would we contain an attack that starts here?&rdquo; These questions help teams internalize security thinking rather than just following rules.</p>
<p>The goal is creating teams that instinctively separate concerns based on security requirements, not just functional requirements. When your team starts proposing separated architectures without being prompted, you know the culture shift is working.</p>
<h2 id="intentional-configuration-building-security-conscious-deployment-culture">Intentional Configuration: Building Security-Conscious Deployment Culture</h2>
<p>Security misconfiguration represents one of the most persistent challenges in engineering leadership because it reveals gaps in team processes and organizational culture. The problem isn&rsquo;t that engineers don&rsquo;t understand security—it&rsquo;s that deployment processes often prioritize speed over security verification.</p>
<p>I&rsquo;ve seen engineering teams that had excellent security knowledge but still suffered issues because their deployment culture didn&rsquo;t include security configuration validation. The issue compounds when teams are under pressure to ship features quickly, making it easy to rationalize skipping security configuration reviews.</p>
<p>The solution isn&rsquo;t just better checklists or automated scanners, though those help. The real challenge is building a deployment culture where security configuration becomes as automatic as running tests. This requires engineering leaders to demonstrate that security configuration is a fundamental part of professional software deployment, not an optional extra.</p>
<p>When I work with teams on configuration security, I focus on three organizational patterns that prevent security misconfiguration:</p>
<ol>
<li><strong>Configuration as Code</strong>: Teams that treat configuration with the same rigor as application code naturally apply security thinking to deployment settings. When configuration changes require code review, security considerations become part of the discussion.</li>
<li><strong>Default-Secure Patterns</strong>: Rather than relying on engineers to remember security settings, establish organizational patterns where secure configuration is the default. This might mean custom deployment templates, infrastructure as code patterns, or automated validation that catches insecure defaults.</li>
<li><strong>Security Configuration Reviews</strong>: Just as you wouldn&rsquo;t deploy code without reviewing it, security configuration should be part of your regular architecture review process. This creates opportunities for knowledge sharing and continuous improvement.</li>
</ol>
<p>Security configuration problems are usually process problems disguised as technical problems.</p>
<blockquote>
<p>When teams consistently deploy with insecure configurations, it&rsquo;s often because their deployment process doesn&rsquo;t include security validation points, not because they lack security knowledge.</p>
</blockquote>
<p>Engineering leaders can transform this by making security configuration visible in deployment workflows. When your team sees you reviewing security settings during deployment reviews, asking questions about default configurations, and prioritizing security hardening alongside feature development, they learn to apply the same standards to their own work.</p>
<p>The goal is creating teams that instinctively question defaults and validate security configurations, not just when prompted by checklists, but because secure configuration has become part of their professional identity as engineers.</p>
<h2 id="controlled-access-designing-authorization-into-team-thinking">Controlled Access: Designing Authorization into Team Thinking</h2>
<p>Access control failures represent a particularly insidious class of security vulnerabilities because they&rsquo;re often invisible until it&rsquo;s too late. Unlike other security issues that can be caught by automated tools, access control problems require human understanding of business logic and user relationships. This makes them a perfect example of how security culture directly impacts security outcomes.</p>
<p>The challenge for engineering leaders is that beyond being a technical problem, access control is a design thinking problem. Teams that build secure access controls don&rsquo;t just implement authorization checks; they also think systematically about user relationships, privilege boundaries, and failure modes during the design phase.</p>
<blockquote>
<p>I&rsquo;ve observed that teams struggling with access control issues often share a common pattern: they build features first and add authorization as an afterthought.</p>
</blockquote>
<p>This approach creates security debt that compounds over time, making it harder to reason about who should have access to what functionality.</p>
<p>The solution requires a shift in how teams approach feature development. Instead of thinking about authorization as something you add to features, security-conscious teams think about authorization as a fundamental constraint that shapes feature design.</p>
<p>Consider the difference between these two approaches when building an admin moderation feature:</p>
<p><strong>Traditional Approach</strong>: Build the moderation interface, then add permission checks to prevent unauthorized access.</p>
<p><strong>Security-First Approach</strong>: Design the moderation feature as a completely separate system with its own authentication context, making unauthorized access architecturally impossible.</p>
<p>The second approach requires more upfront planning, but it creates systems that are secure by design rather than secure by careful (and slower, and more costly) implementation. More importantly, it teaches teams to think about authorization as a design constraint, not just a technical requirement.</p>
<p>This shift in thinking has organizational implications beyond just security. When teams consistently design features with authorization constraints in mind, they develop better intuition about user workflows, system boundaries, and API design. The security thinking improves overall system design.</p>
<blockquote>
<p>The goal is creating teams that instinctively design authorization into features rather than retrofitting it after implementation.</p>
</blockquote>
<p>As engineering leaders, we can foster this thinking by making authorization design visible in architecture discussions. When reviewing feature proposals, ask questions like: &ldquo;Who needs access to this?&rdquo; and &ldquo;How would we prevent privilege escalation?&rdquo; These questions help teams internalize authorization thinking as part of their design process.</p>
<h2 id="from-architecture-to-culture-the-leadership-impact">From Architecture to Culture: The Leadership Impact</h2>
<p>The three architectural principles I&rsquo;ve outlined—strategic separation, intentional configuration, and controlled access—represent more than just technical best practices. They&rsquo;re tools for building engineering cultures that make security decisions instinctively rather than reactively.</p>
<p>The transformation happens when engineering leaders consistently demonstrate that security considerations are integral to professional software development. When your team sees you making architectural decisions that prioritize security boundaries, questioning configuration defaults, and designing authorization into features from the start, they learn to apply the same thinking to their own work.</p>
<blockquote>
<p>This cultural shift has organizational benefits that extend far beyond security. Teams that think systematically about security boundaries also design better APIs, create more maintainable systems, and build more robust software overall. This security thinking improves engineering decision-making across the board.</p>
</blockquote>
<p>Security thinking isn&rsquo;t something you can successfully mandate through policies or checklists, though I’ve seen many organizations try. It emerges from the accumulated architectural decisions your team makes and the frameworks they internalize for thinking about system design. When security considerations become part of how your team naturally approaches technical problems, you&rsquo;ve created something much more valuable than just secure applications—you&rsquo;ve built a team that can adapt to new security challenges as they (constantly) emerge.</p>
<p>As engineering leaders, our role isn&rsquo;t just to ensure our current systems are secure, but to build teams that will continue making security-conscious decisions as they face new challenges, technologies, and organizational pressures. The architectural principles we establish today define the security culture that will guide our teams through future unknowns.</p>
]]></content></entry><entry><title type="html">Migrating to the cloud but without screwing it up, or how to move house</title><link href="https://victoria.dev/archive/migrating-to-the-cloud-but-without-screwing-it-up-or-how-to-move-house/"/><id>https://victoria.dev/archive/migrating-to-the-cloud-but-without-screwing-it-up-or-how-to-move-house/</id><author><name>Victoria Drake</name></author><published>2019-09-23T08:03:12-04:00</published><updated>2019-09-23T08:03:12-04:00</updated><content type="html"><![CDATA[<p>For an application that&rsquo;s ready to scale, not using managed cloud architecture these days is like insisting on digging your own well for water. It&rsquo;s far more labour-intensive, requires buying all your own equipment, takes a lot more time, and there&rsquo;s a higher chance you&rsquo;re going to get it wrong because you don&rsquo;t personally have a whole lot of experience digging wells, anyway.</p>
<p>That said - let&rsquo;s just get this out of the way first - there is no cloud. It&rsquo;s just someone else&rsquo;s computer.</p>
<p>Of course, these days, cloud services go far beyond the utility we&rsquo;d expect from a single computer. Besides being able to quickly set up and utilize the kind of computing power that previously required a new office lease agreement to house, there are now a multitude of monitoring, management, and analysis tools at our giddy fingertips. While it&rsquo;s important to understand that the cloud isn&rsquo;t a better option in every case, for applications that can take advantage of it, we can do more, do it faster, and do it for less money than if we were to insist on building our own on-premises infrastructure.</p>
<p>That&rsquo;s all great, and easily said; moving to the cloud, however, can look from the outset like a pretty daunting task. How, exactly, do we go about shifting what may be years of on-premises data and built-up systems to <em>someone else&rsquo;s computer?</em> You know, without being able to see it, touch it, and without completely screwing up our stuff.</p>
<p>While it probably takes less work and money than setting up or maintaining the same architecture on-premise, it does take some work to move to the cloud initially. It&rsquo;s important that our application is prepared to migrate, and capable of using the benefits of cloud services once it gets there. To accomplish this, and a smooth transition, preparation is key. In fact, it&rsquo;s a whole lot like moving to a new house.</p>
<p>In this article, we&rsquo;ll take a high-level look at the general stages of taking an on-premise or self-hosted application and moving it to the cloud. This guide is meant to serve as a starting point for designing the appropriate process for your particular situation, and to enable you to better understand the cloud migration process. While cloud migration may not be the best choice for some applications - such as ones without scalable architecture or where very high computing resources are needed - a majority of modular and modern applications stand to benefit from a move to the cloud.</p>
<p>It&rsquo;s certainly possible, as I discovered at a recent event put on by <a href="https://aws.amazon.com/">Amazon Web Services</a> (AWS) Solutions Architects, to migrate smoothly and efficiently, with near-zero loss of availability to customers. I&rsquo;ll specifically reference some services provided by AWS, however, similar functionality can be found with other cloud providers. I&rsquo;ve found the offerings from AWS to be pleasantly modular in scope, which is why I use them myself and why they make good examples for discussing general concepts.</p>
<p>To have our move go as smoothly as possible, here are the things we&rsquo;ll want to consider:</p>
<ol>
<li>The type of move we&rsquo;re making;</li>
<li>The things we&rsquo;ll take, and the things we&rsquo;ll clean up;</li>
<li>How to choose the right type and size for the infrastructure we&rsquo;re moving into; and</li>
<li>How to do test runs to practice for the big day.</li>
</ol>
<h2 id="the-type-of-move-were-making">The type of move we&rsquo;re making</h2>
<p>While it&rsquo;s important to understand why we&rsquo;re moving our application to cloud services, we should also have an idea of what we&rsquo;d like it to look like when it gets there. There are three main ways to move to the cloud: re-host, re-platform, or re-factor.</p>
<h3 id="re-host">Re-host</h3>
<p>A re-host scenario is the the most straightforward type of move. It involves no change to the way our application is built or how it runs. For example, if we currently have Python code, use PostgreSQL, and serve our application with Apache, a re-host move would mean we use all the same components, combined in just the same way, only now they&rsquo;re in the cloud. It&rsquo;s a lot like moving into a new house that has the exact same floor plan as the current one. All the furniture goes into the same room it&rsquo;s in now, and it&rsquo;s going to feel pretty familiar when we get there.</p>
<p>The main draw of a re-host move is that it may offer the least amount of complication necessary in order to take advantage of going to the cloud. Scalable applications, for example, can gain the ability to automatically manage necessary application resources.</p>
<p>While re-hosting makes scaling more automatic, it&rsquo;s important to note that it won&rsquo;t in itself make an application scalable. If the application infrastructure is not organized in such a way that gives it the ability to scale, a re-factor may be necessary instead.</p>
<h3 id="re-platform">Re-platform</h3>
<p>If a component of our current application set up isn&rsquo;t working out well for us, we&rsquo;re probably going to want to re-platform. In this case, we&rsquo;re making a change to at least one component of our architecture; for example, switching our database from Oracle to MySQL on <a href="https://aws.amazon.com/rds/">Amazon Relational Database Service</a> (RDS).</p>
<p>Like moving from a small apartment in Tokyo to an equally small apartment in New York, a re-platform doesn&rsquo;t change the basic nature of our application, but does change its appearance and environment. In the database change example, we&rsquo;ll have all the same data, just organized or formatted a little differently. In most cases, we won&rsquo;t have to make these changes manually. A tool such as <a href="https://aws.amazon.com/dms/">Amazon Database Migration Service</a> (DMS) can help to seamlessly shift our data over to the new database.</p>
<p>We might re-platform in order to enable us to better meet a business demand in the future, such as scaling up, integrating with other technological components, or choosing a more modern technology stack.</p>
<h3 id="re-factor">Re-factor</h3>
<p>A move in which we re-factor our application is necessarily more complicated than our other options, however, it may provide the most overall benefit for companies or applications that have reason to make this type of move. As with code, refactoring is done when fundamental changes need to be made in order for our application to meet a business need. The specifics necessarily differ case-by-case, but typically involve changes to architectural components or how those components relate to one another. This type of move may also involve changing application code in order to optimize the application&rsquo;s performance in a cloud environment. We can think of it like moving out from our parent&rsquo;s basement in the suburbs and getting a nice townhouse in the city. There&rsquo;s no way we&rsquo;re taking that ancient hand-me-down sofa, so we&rsquo;ll need some new furniture, and for our neighbour&rsquo;s sake, probably window dressings.</p>
<p>Refactoring may enable us to modernize a dated application, or make it more efficient in general. With greater efficiency, we can better take advantage of services that cloud providers typically offer, like bursting resources or attaining deep analytical insight.</p>
<p>If a re-factor is necessary but time is scarce, it may be better to re-host or re-platform first, then re-factor later. That way, we&rsquo;ll have a job well done later instead of a hasty, botched migration (and more problems) sooner.</p>
<h2 id="what-to-take-and-what-to-clean-up">What to take, and what to clean up</h2>
<p>Over the years of living in one place, stuff tends to pile up unnoticed in nooks and crannies. When moving house, it&rsquo;s usually a great opportunity to sort everything out and decide what is useful enough to keep, and what should be discarded or given away. Moving to the cloud is a similarly great opportunity to do the same when it comes to our application.</p>
<p>While cloud storage is inexpensive nowadays, there may be some things that don&rsquo;t make sense to store any longer, or at least not keep stored with our primary application. If data cannot be discarded due to policy or regulations, we may choose a different storage class to house data that we don&rsquo;t expect to need anytime soon outside of our main application.</p>
<p>In the case of <a href="https://aws.amazon.com/s3/">Amazon&rsquo;s Simple Storage Service</a> (S3), we can choose to use different <a href="https://aws.amazon.com/s3/storage-classes/">storage classes</a> that accomplish this goal. While the data that our business relies on every day can take advantage of the Standard class 99.99% availability, data meant for long-term cold storage such as archival backups can be put into the Glacier class, which has longer retrieval time and lower cost.</p>
<h2 id="the-right-type-and-size">The right type and size</h2>
<p>Choosing the type and size of cloud infrastructure appropriate for our business is usually the part that can be the most confusing. How should we predict, in a new environment or for a growing company, the computing power we&rsquo;ll need?</p>
<p>Part of the beauty of not procuring hardware on our own is that won&rsquo;t have to make predictions like these. Using cloud storage and instances, expanding or scaling back resources can be done in a matter of minutes, sometimes seconds. With managed services, it can even be done automatically for us. With the proper support for scalability in our application, it&rsquo;s like having a magical house that instantly generates any type of room and amenity we need at that moment. The ability to continually ensure that we&rsquo;re using appropriate, cost-effective resources is at our fingertips, and often clearly visualized in charts and dashboards.</p>
<p>For applications new to the cloud, some leeway for experimentation may be necessary. While cloud services enables us to quickly spin up and try out different architectures, there&rsquo;s no guarantee that all of those set ups will work well for our application. For example, running a single instance may be <a href="http://einaregilsson.com/serverless-15-percent-slower-and-eight-times-more-expensive/">less expensive than going serverless</a>, but we&rsquo;d be hard pressed to know this until we tried it out.</p>
<p>As a starting point, we simply need enough storage and computing power to support the application as it is currently running, today. For example, in the case of storage, consider the size of the current database - the actual database data, not the total storage capacity of hardware on-premises. For a detailed cost exploration, AWS even offers a <a href="https://calculator.s3.amazonaws.com/index.html">Simple Monthly Calculator</a> with use case samples to help guide expectations.</p>
<h2 id="do-test-runs-before-the-big-day">Do test runs before the big day</h2>
<p>Running a trial cloud migration may be an odd concept, but it is an essential component to ensuring that the move goes as planned with minimal service interruption. Imagine the time and energy that would be saved in the moving house example if we could automate test runs! Invariably, some box or still-hung picture is forgotten and left out of the main truck, necessitating additional trips in other vehicles. With multiple chances to ensure we&rsquo;ve got it down pat, we minimize the possibility that our move causes any break in normal day-to-day business.</p>
<p>Generally, to do a test run, we create a duplicate version of our application. The more we can duplicate, the more thorough the test run will be, especially if our data is especially large. Though duplication may seem tedious, working with the actual components we intend to migrate is essential to ensuring the migration goes as planned. After all, if we only did a moving-house test run with one box, it wouldn&rsquo;t be very representative.</p>
<p>Test runs can help to validate our migration plan against any challenges we may encounter. These challenges might include:</p>
<ul>
<li>Downtime restrictions;</li>
<li>Encrypting data in transit and immediately when at rest on the target;</li>
<li>Schema conversion to a new target schema (the <a href="https://aws.amazon.com/dms/schema-conversion-tool/">AWS Schema Conversion Tool</a> can also help);</li>
<li>Access to databases, such as through firewalls or VPNs;</li>
<li>Developing a process to ensure that all the data successfully migrated, such as by using a hash function.</li>
</ul>
<p>Test runs also help to give us a more accurate picture of the overall time that a migration will take, as well as affording us the opportunity to fine-tune it. Factors that may affect the overall speed of a migration include:</p>
<ul>
<li>The sizes of the source and target instances;</li>
<li>Available bandwidth for moving data;</li>
<li>Schema configurations; and</li>
<li>Transaction pressure on the source, such as changes to the data and the volume of incoming transactions.</li>
</ul>
<p>Once the duplicate application has been migrated via one or more <a href="https://aws.amazon.com/cloud-data-migration/">options</a>, we test the heck out of the application that&rsquo;s now running in the cloud to ensure it performs as expected. Ideally, on the big day, we&rsquo;d follow this same general process to move up-to-date duplicate data, and then seamlessly point the &ldquo;real&rdquo; application or web address to the new location in the cloud. This means that our customers experience near-zero downtime; essentially, only the amount of time that the change in location-pointing would need to propagate to their device.</p>
<p>In the case of very large or complex applications with many components or many teams working together at the same time, a more gradual approach may be more appropriate than the &ldquo;Big Bang&rdquo; approach, and may help to mitigate risk of any interruptions. This means migrating in stages, component by component, and running tests between stages to ensure that all parts of the application are communicating with each other as expected.</p>
<h2 id="preparation-is-essential-to-a-smooth-migration">Preparation is essential to a smooth migration</h2>
<p>I hope this article has enabled a more practical understanding of how cloud migration can be achieved. With thorough preparation, it&rsquo;s possible to take advantage of all the cloud has to offer, with minimal hassle to get there.</p>
<p>My thanks to the AWS Solutions Architects who presented at Pop-Up Loft and shared their knowledge on these topics, in particular: Chandra Kapireddy, Stephen Moon, John Franklin, Michael Alpaugh, and Priyanka Mahankali.</p>
<p>One last nugget of wisdom, courtesy of John: &ldquo;Friends don&rsquo;t let friends use DMS to create schema objects.&rdquo;</p>
]]></content></entry><entry><title type="html">How users and applications stay safe on the Internet: it&amp;#39;s proxy servers all the way down</title><link href="https://victoria.dev/archive/how-users-and-applications-stay-safe-on-the-internet-its-proxy-servers-all-the-way-down/"/><id>https://victoria.dev/archive/how-users-and-applications-stay-safe-on-the-internet-its-proxy-servers-all-the-way-down/</id><author><name>Victoria Drake</name></author><published>2019-09-16T09:35:28-04:00</published><updated>2019-09-16T09:35:28-04:00</updated><content type="html"><![CDATA[<p>Both Internet users and Internet-connected applications can benefit from investing in cybersecurity. One core aspect of online privacy is the use of a proxy server, though this basic building block may not be initially visible underneath its more recognizable forms. Proxy servers are a useful thing to know about nowadays, for developers, software product owners, as well as the average dog on the Internet. Let&rsquo;s explore what makes proxy servers an important piece of cybersecurity support.</p>
<blockquote>
<p>&ldquo;On the Internet, nobody knows you&rsquo;re a dog.&rdquo;</p>
</blockquote>
<p>When <a href="https://en.wikipedia.org/wiki/On_the_Internet,_nobody_knows_you%27re_a_dog">Peter Steiner&rsquo;s caption</a> was first published in The New Yorker in 1993, it reportedly went largely unnoticed. Only later did the ominous and slightly frightening allusion to online anonymity touch the public consciousness with the icy fingers of the unknown. As Internet usage became more popular, users became concerned that other people could represent themselves online in any manner they chose, without anyone else knowing who they truly were.</p>
<p>This, to make a gross understatement, is no longer the case. Thanks to <a href="https://support.mozilla.org/en-US/kb/enable-and-disable-cookies-website-preferences">tracking cookies</a>, <a href="https://robertheaton.com/2017/10/17/we-see-you-democratizing-de-anonymization/">browser fingerprinting</a>, <a href="https://www.privacypolicies.com/blog/isp-tracking-you/">Internet Service Providers (ISPs) selling our browsing logs to advertisers</a>, and our own inexplicable inclination to put our names and faces on social networks, online anonymity is out like last year&rsquo;s LaCroix flavours. While your next-door neighbor may not know how to find you online (well, except for through that location-based secondhand marketplace app you&rsquo;re using), you can be certain that at least one large advertising company has a series of zeroes and ones somewhere that represent you, the specific details of your market demographic, and all your online habits, including your preferred flavour of LaCroix.</p>
<p>There are ways to add <em>some</em> layers of obscurity, like using a corporate firewall that hides your IP, or <a href="https://www.torproject.org/">using Tor</a>. The underlying mechanism of both these methods is the same. Like being enshrouded in the layers of an onion, we&rsquo;re using one or more <a href="https://en.wikipedia.org/wiki/Proxy_server">proxy servers</a> to shield our slightly sulfuric selves from third-party tracking.</p>
<h2 id="whats-a-proxy-server-anyway">What&rsquo;s a proxy server, anyway</h2>
<p>A proxy, in the traditional English definition, is the &ldquo;authority or power to act for another.&rdquo; (<a href="https://www.merriam-webster.com/dictionary/proxy">Merriam-Webster</a>) A proxy server, in the computing context, is a server that acts on behalf of another server, or a user&rsquo;s machine.</p>
<p>By using a proxy to browse the Internet, for example, a user can defer being personally identifiable. All of the user&rsquo;s Internet traffic appears to come from the proxy server instead of their machine.</p>
<h2 id="proxy-servers-are-for-users">Proxy servers are for users</h2>
<p>There are a few ways that we, as the client, can use a proxy server to conceal our identity when we go online. It&rsquo;s important to know that these methods offer differing levels of anonymity, and that no single method will really provide <em>true</em> anonymity; if others are actively seeking to find you on the Internet, for whatever reason, further steps should be taken to make your activity truly difficult to identify. (Those steps are beyond the scope of this article, but you can get started with the <a href="https://ssd.eff.org/">Electronic Frontier Foundation&rsquo;s (EFF) Surveillance Self-Defense</a> resource.) For the average user, however, here is a small menu of options ranging from least to most anonymous.</p>
<h3 id="use-a-proxy-in-your-web-browser">Use a proxy in your web browser</h3>
<p>Certain web browsers, including Firefox and Safari on Mac, allow us to configure them to send our Internet traffic through a proxy server. The proxy server attempts to <a href="https://en.wikipedia.org/wiki/Anonymizer">anonymize</a> our requests by replacing our originating IP address with the proxy server&rsquo;s own IP. This provides us with some anonymity, as the website we&rsquo;re trying to reach will not see our originating IP address; however, the proxy server that we choose to use will know exactly who originated the request. This method also doesn&rsquo;t necessarily encrypt traffic, block cookies, or stop social media and cross-site trackers from following us around; on the upside, it&rsquo;s the method least likely to prevent websites that use cookies from functioning properly.</p>
<p><img src="browser-proxy.png" alt="A cartoon of a proxy server guarding a browser"></p>
<p>Public proxy servers are out there, and deciding whether or not we should use any one of them is on par with deciding whether we should eat a piece of candy handed to us by a smiling stranger. If your academic institution or company provides a proxy server address, it is (hopefully) a private server with some security in place. My preferred method, if we have a little time and a few monthly dollars to invest in our security, is to set up our own virtual instance with a company such as <a href="https://aws.amazon.com/ec2/">Amazon Web Services</a> or <a href="https://www.digitalocean.com/products/droplets/">Digital Ocean</a> and use this as our proxy server.</p>
<p>To use a proxy through our browser, we can <a href="https://support.mozilla.org/en-US/kb/connection-settings-firefox">edit our Connection Settings in Firefox</a>, or <a href="https://support.apple.com/guide/safari/set-up-a-proxy-server-ibrw1053/mac">set up a proxy server using Safari on Mac</a>.</p>
<p>In regards to choosing a browser, I would happily recommend <a href="https://www.mozilla.org/en-US/firefox/new/">Firefox</a> to any Internet user who wants to beef up the security of their browsing experience right out of the box. Mozilla has been a champion of privacy-first since I&rsquo;ve heard of them, and recently made some well-received changes to <a href="https://blog.mozilla.org/blog/2019/06/04/firefox-now-available-with-enhanced-tracking-protection-by-default/">Enhanced Tracking Protection in Firefox Browser</a> that blocks social media trackers, cross-site tracking cookies, fingerprinters, and cryptominers by default.</p>
<h3 id="use-a-vpn-on-your-device">Use a VPN on your device</h3>
<p>In order to take advantage of a proxy server for all our Internet usage instead of just through one browser, we can use a Virtual Private Network (VPN). A VPN is a service, usually paid, that sends our Internet traffic through their servers, thus acting as a proxy. A VPN can be used on our laptop as well as phone and tablet devices, and since it encompasses all our Internet traffic, it doesn&rsquo;t require much extra effort to use other than ensuring our device is connected. Using a VPN is an effective way to keep nosy ISPs from snooping on our requests.</p>
<p><img src="vpn.png" alt="A cartoon depicting a private VPN"></p>
<p>To use a paid, third-party VPN service, we&rsquo;d usually sign up on their website and download their app. It&rsquo;s important to keep in mind that whichever provider we choose, we&rsquo;re entrusting them with our data. VPN providers anonymize our activity from the Internet, but can themselves see all our requests. Providers vary in terms of their privacy policies and the data they choose to log, so a little research may be necessary to determine which, if any, we are comfortable trusting.</p>
<p>We can also roll our own VPN service by using a virtual instance and <a href="https://openvpn.net/">OpenVPN</a>. OpenVPN is an open source VPN protocol, and can be used with a few virtual instance providers, such as <a href="https://openvpn.net/amazon-cloud/">Amazon VPC</a>, <a href="https://openvpn.net/microsoft-azure/">Microsoft Azure</a>, <a href="https://openvpn.net/google-cloud-vpn/">Google Cloud</a>, and <a href="https://openvpn.net/digital-ocean-vpn/">Digital Ocean Droplets</a>. I previously wrote a tutorial on <a href="/blog/how-to-set-up-openvpn-on-aws-ec2-and-fix-dns-leaks-on-ubuntu-18.04-lts/">setting up your own personal VPN service with AWS</a> using an EC2 instance. I&rsquo;ve been running this solution personally for about a month, and it&rsquo;s cost me almost $4 USD in total, which is a price I&rsquo;m quite comfortable paying for some peace of mind.</p>
<h3 id="use-tor">Use Tor</h3>
<p>Tor takes the anonymity offered by a proxy server and compounds it by forwarding our requests through a <a href="https://en.wikipedia.org/wiki/Relay_network">relay network</a> of other servers, each called a &ldquo;node.&rdquo; Our traffic passes through three nodes on its way to a destination: the <em>guard</em>, <em>middle</em>, and <em>exit</em> nodes. At each step, the request is encrypted and anonymized such that the current node only knows where to send it, and nothing more about what the request contains. This separation of knowledge means that, of the options discussed, Tor provides the most complete version of anonymity. (For a more complete explanation, see <a href="https://robertheaton.com/2019/04/06/how-does-tor-work/">Robert Heaton&rsquo;s article on how Tor works</a>, which is so excellently done that I wish I&rsquo;d written it myself.)</p>
<p><img src="tor.png" alt="Tor onion holding a Free Hugs sign"></p>
<p>That said, this level of anonymity comes with its own cost. Not monetary, as <a href="https://www.torproject.org/download/">Tor Browser</a> is free to download and use. It is, however, slower than using a VPN or simple proxy server through a browser, due to the circuitous route our requests take.</p>
<h2 id="proxy-servers-are-for-servers-too">Proxy servers are for servers too</h2>
<p>We&rsquo;re now familiar with proxy servers in the context of protecting users as they surf the web, but proxies aren&rsquo;t just for clients. Websites and Internet-connected applications can use <a href="https://en.wikipedia.org/wiki/Reverse_proxy">reverse proxy servers</a> for obfuscation too. The &ldquo;reverse&rdquo; part just means that the proxy is acting on behalf of the server, instead of the client.</p>
<p>Why would a web server care about anonymity? Generally, they don&rsquo;t, at least not in the same way some users do. Web servers can benefit from using a proxy for a few different reasons; for example, they typically offer faster service to users by <a href="https://en.wikipedia.org/wiki/Web_cache">caching</a> or <a href="https://en.wikipedia.org/wiki/HTTP_compression">compressing</a> content to optimize delivery. From a cybersecurity perspective, however, a reverse proxy can improve an application&rsquo;s security posture by obfuscating the underlying infrastructure.</p>
<p><img src="syllables.png" alt="A cartoon making fun of the big words I used"></p>
<p>Basically, by placing another web server (the &ldquo;proxy&rdquo;) in front of the web server that directly accesses all the files and assets, we make it more difficult for an attacker to pinpoint our &ldquo;real&rdquo; web server and mess with our stuff. Like when you want to see the store manager and the clerk you&rsquo;re talking to says, &ldquo;I speak for the manager,&rdquo; and you&rsquo;re not really sure there even <em>is</em> a manager, anyway, but you successfully exchange the hot pink My Little Pony they sold you for a <em>fuchsia</em> one, thankyouverymuch, so now you&rsquo;re no longer concerned with who the manager is and whether or not they really exist, and if you passed them on the street you would not be able to stop them and call them out for passing off hot pink as fuchsia, and the manager is just fine with that.</p>
<p>Some common web servers can also act as reverse proxies, often with just a minimal and straightforward configuration change. While the best choice for your particular architecture is unknown to me, I will offer a couple common examples here.</p>
<h3 id="using-nginx-as-a-reverse-proxy">Using NGINX as a reverse proxy</h3>
<p>NGINX uses the <code>proxy_pass</code> directive in its <a href="https://docs.nginx.com/nginx/admin-guide/basic-functionality/managing-configuration-files/">configuration file</a> (<code>nginx.conf</code> by default) to turn itself into a reverse proxy server. The set up requires the following lines to be placed in the configuration file:</p>
<pre tabindex="0"><code class="language-conf" data-lang="conf">location /requested/path/ {
    proxy_pass http://www.example.com/target/path/;
}
</code></pre><p>This specifies that all requests for the path <code>/requested/path/</code> are forwarded to <code>http://www.example.com/target/path/</code>. The target can be a domain name or an IP address, the latter with or without a port.</p>
<p>The full <a href="https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/">guide to using NGINX as a reverse proxy</a> is part of the NGINX documentation.</p>
<h3 id="using-apache-httpd-as-a-reverse-proxy">Using Apache httpd as a reverse proxy</h3>
<p>Apache httpd similarly requires some straightforward configuration to act as a reverse proxy server. In the <a href="https://httpd.apache.org/docs/current/configuring.html">configuration file</a>, usually <code>httpd.conf</code>, set the following directives:</p>
<pre tabindex="0"><code class="language-conf" data-lang="conf">ProxyPass &#34;/requested/path/&#34;  &#34;http://www.example.com/target/path/&#34;
ProxyPassReverse &#34;/requested/path/&#34;  &#34;http://www.example.com/target/path/&#34;
</code></pre><p>The <code>ProxyPass</code> directive ensures that all requests for the path <code>/requested/path/</code> are forwarded to <code>http://www.example.com/target/path/</code>. The <code>ProxyPassReverse</code> directive ensures that the headers sent by the web server are modified to point to the reverse proxy server instead.</p>
<p>The full <a href="https://httpd.apache.org/docs/2.4/howto/reverse_proxy.html">reverse proxy guide for Apache HTTP server</a> is available in their documentation.</p>
<h2 id="proxy-servers-_most-of_-the-way-down">Proxy servers <em>most of</em> the way down</h2>
<p>I concede that my title is a little facetious, as cybersecurity best practices aren&rsquo;t really some eternal infinite-regression mystery (though they may sometimes seem to be). Regardless, I hope this post has helped in your understanding of what proxy servers are, how they contribute to online anonymity for both clients and servers, and that they are an integral building block of cybersecurity practices.</p>
<p>If you&rsquo;d like to learn more about personal best practices for online security, I highly recommend exploring the articles and resources provided by <a href="https://www.eff.org/">EFF</a>. For a guide to securing web sites and applications, the <a href="https://github.com/OWASP/CheatSheetSeries">OWASP Cheat Sheet Series</a> is a fantastic resource.</p>
]]></content></entry><entry><title type="html">Building Data Protection Culture: Why Engineering Leaders Must Address the Human Side of Security</title><link href="https://victoria.dev/posts/building-data-protection-culture-why-engineering-leaders-must-address-the-human-side-of-security/"/><id>https://victoria.dev/posts/building-data-protection-culture-why-engineering-leaders-must-address-the-human-side-of-security/</id><author><name>Victoria Drake</name></author><published>2019-09-09T09:10:11-04:00</published><updated>2019-09-09T09:10:11-04:00</updated><content type="html"><![CDATA[<p>The most frustrating security incidents I&rsquo;ve dealt with as an engineering leader weren&rsquo;t caused by sophisticated attacks or zero-day vulnerabilities. They were caused by well-intentioned team members who accidentally exposed sensitive data through everyday tools and processes. A developer pasting API keys into a public Slack channel. A support engineer sharing database credentials through an unsecured text-sharing service. A product manager including real customer data in a publicly accessible report.</p>
<p>These incidents reveal a fundamental truth about data protection: it&rsquo;s not primarily a technical problem—it&rsquo;s an organizational one. The security of our applications depends as much on how our teams handle sensitive data in their daily workflows as it does on our encryption algorithms or access controls.</p>
<p>The challenge for engineering leaders is that traditional security approaches focus on technical controls while overlooking the human systems that determine how data actually flows through our organizations. When we treat data protection as purely a technical problem, we create a gap between our security policies and the reality of how our teams work.</p>
<blockquote>
<p>Teams with the strongest data protection practices don&rsquo;t rely on security tools. They have organizational cultures that make secure data handling feel natural and obvious.</p>
</blockquote>
<p>Building this kind of culture requires engineering leaders to think beyond technical solutions and address the underlying organizational patterns that lead to data exposure. The goal isn&rsquo;t just to prevent specific incidents, but to create teams that instinctively handle sensitive data securely, even under pressure.</p>
<h2 id="the-reality-of-data-exposure-its-happening-right-now">The Reality of Data Exposure: It&rsquo;s Happening Right Now</h2>
<p>Before diving into solutions, it&rsquo;s worth understanding just how pervasive data exposure has become. The reality is that sensitive data from organizations of all sizes is readily discoverable through simple search techniques. A quick search for <code>site:pastebin.com &quot;api_key&quot;</code> or <code>site:github.com &quot;password&quot;</code> reveals thousands of exposed credentials, database connections, and API keys from companies around the world.</p>
<p>This isn&rsquo;t theoretical—it&rsquo;s happening to teams just like yours, right now. The <a href="https://www.exploit-db.com/google-hacking-database">Google Hacking Database</a> catalogs thousands of search queries that can expose sensitive data, and security researchers regularly discover new leaked credentials on platforms like Pastebin, GitHub, and even internal Slack channels that have been accidentally made public.</p>
<figure class="screenshot"><img src="/posts/building-data-protection-culture-why-engineering-leaders-must-address-the-human-side-of-security/pastebin_apikey.png"
    alt="A screenshot of exposed api key in Google search"><figcaption>
      <p>API keys exposed through public paste services—a common data exposure pattern.</p>
    </figcaption>
</figure>

<p>The scale of this problem reveals something important: data exposure isn&rsquo;t just a technical failure, it&rsquo;s a systematic organizational problem. When thousands of developers across hundreds of companies make the same types of mistakes, it suggests that our industry-wide approach to data protection is fundamentally flawed.</p>
<h2 id="the-leadership-challenge-beyond-technical-solutions">The Leadership Challenge: Beyond Technical Solutions</h2>
<p>Most engineering leaders approach data protection through technical controls: better encryption, more restrictive access policies, automated scanning tools. These controls are important, but they don&rsquo;t address the root cause of most data exposure incidents.</p>
<p>The real challenge is organizational. When team members expose sensitive data, it&rsquo;s usually because they&rsquo;re working around limitations in their tools or processes. They&rsquo;re not necessarily being careless—they&rsquo;re trying to get their work done efficiently within the constraints of their environment.</p>
<p>Consider these common scenarios:</p>
<p><strong>The Developer&rsquo;s Dilemma</strong>: A developer needs to share a database connection string with a colleague to debug a production issue. The &ldquo;secure&rdquo; process involves filing a ticket, waiting for approval, and scheduling a meeting. The expedient process involves pasting it into Slack. Under deadline pressure, which do you think happens more often?</p>
<p><strong>The Support Engineer&rsquo;s Challenge</strong>: A support engineer needs to share customer data with the product team to investigate a bug. The secure process requires anonymizing the data, which takes time they don&rsquo;t have. The expedient process involves copying real data into a shared document.</p>
<p><strong>The Product Manager&rsquo;s Bind</strong>: A product manager needs to create test data for a demo. The secure process involves generating fake data that matches production patterns. The expedient process involves copying a subset of real customer data.</p>
<p>In each case, the data exposure isn&rsquo;t caused by malicious intent or even carelessness—it&rsquo;s caused by organizational friction between security requirements and operational reality.</p>
<blockquote>
<p>The most effective approach to data protection isn&rsquo;t to increase security friction, but to reduce the friction around secure practices while making insecure practices more obviously problematic.</p>
</blockquote>
<p>This is where engineering leadership becomes crucial. Technical solutions alone can&rsquo;t bridge the gap between security policies and operational needs. That requires organizational changes that make secure data handling the easiest and most obvious way to work.</p>
<h2 id="building-organizational-capabilities-for-data-protection">Building Organizational Capabilities for Data Protection</h2>
<p>The teams I&rsquo;ve worked with that have the strongest data protection practices share three organizational capabilities:</p>
<h3 id="1-secure-by-default-tooling">1. Secure-by-Default Tooling</h3>
<p>Instead of relying on people to remember security practices, these teams build security into their daily tools. This might mean:</p>
<ul>
<li><strong><strong>Internal paste services</strong></strong> that automatically expire and require authentication instead of relying on external tools</li>
<li><strong><strong>Automated credential management</strong></strong> through tools like AWS Secrets Manager or HashiCorp Vault that make secure credential sharing easier than insecure sharing</li>
<li><strong><strong>Data anonymization tools</strong></strong> that make it trivial to generate realistic test data without using production data</li>
</ul>
<p>People will use the most convenient option available. If secure practices are more convenient than insecure practices, people will choose secure practices naturally.</p>
<h3 id="2-visible-security-boundaries">2. Visible Security Boundaries</h3>
<p>Teams with strong data protection practices make security boundaries obvious in their workflows. This includes:</p>
<ul>
<li><strong><strong>Clear data classification</strong></strong> that helps team members understand what constitutes sensitive data</li>
<li><strong><strong>Workflow integration</strong></strong> that flags when someone is about to share sensitive data through insecure channels</li>
<li><strong><strong>Regular security boundary discussions</strong></strong> during architectural reviews and design meetings</li>
</ul>
<p>When security boundaries are visible and well-understood, team members can make informed decisions about data handling without needing to become security experts.</p>
<h3 id="3-treating-security-mistakes-as-learning-opportunities">3. Treating Security Mistakes as Learning Opportunities</h3>
<p>Perhaps most importantly, these teams create environments where people feel encouraged to report security mistakes or near-misses. This cultural element is often overlooked, but it&rsquo;s crucial for continuous improvement.</p>
<p>Teams that punish security mistakes create incentives for people to hide problems rather than fix them. Teams that treat security mistakes as learning opportunities create incentives for people to surface issues early and help improve processes.</p>
<h2 id="the-engineering-leaders-role-creating-sustainable-change">The Engineering Leader&rsquo;s Role: Creating Sustainable Change</h2>
<p>Building these organizational capabilities requires engineering leaders to approach data protection as a change management challenge, not just a technical one. This means:</p>
<ol>
<li><strong>Making the Case for Investment</strong>: Security tooling and process improvements often require upfront investment that may not have immediate visible returns. Engineering leaders need to advocate for this investment by helping stakeholders understand the organizational costs of data exposure incidents.</li>
<li><strong>Modeling Secure Behavior</strong>: When engineering leaders consistently demonstrate secure data handling practices in their own work, it signals to the team that these practices are valued and expected.</li>
<li><strong>Addressing Process Gaps</strong>: When team members work around security processes, it&rsquo;s often because those processes don&rsquo;t meet their operational needs. Engineering leaders need to identify and address these gaps rather than simply enforcing compliance.</li>
<li><strong>Celebrating Security Improvements</strong>: Teams that recognize and celebrate security improvements create cultures where security work is valued rather than seen as overhead.</li>
</ol>
<p>The goal isn&rsquo;t to eliminate all possibility of data exposure—that&rsquo;s impractical.</p>
<blockquote>
<p>The goal is to build organizational capabilities that make data exposure increasingly unlikely and ensure that when incidents do occur, they&rsquo;re caught and resolved quickly.</p>
</blockquote>
<h2 id="from-reactive-to-proactive-building-long-term-security-culture">From Reactive to Proactive: Building Long-Term Security Culture</h2>
<p>The most successful engineering leaders I&rsquo;ve worked with approach data protection as an ongoing organizational capability rather than a one-time project. This means:</p>
<ol>
<li><strong>Regular Security Culture Assessment</strong>: Periodically evaluating whether your team&rsquo;s tools and processes support secure data handling or create friction that encourages workarounds.</li>
<li><strong>Continuous Tool Investment</strong>: Investing in tools and processes that make secure data handling easier and more convenient than insecure alternatives.</li>
<li><strong>Cross-Functional Security Discussions</strong>: Including security considerations in product planning, design reviews, and operational discussions rather than treating security as a separate concern.</li>
<li><strong>Security Skill Development</strong>: Helping team members develop the knowledge and judgment needed to make good security decisions in novel situations.</li>
</ol>
<p>The teams that excel at data protection have built organizational cultures where security is valued as part of the development lifecycle. This cultural shift doesn&rsquo;t happen overnight, but it creates sustainable security practices that adapt to new challenges and technologies.</p>
<p>As engineering leaders, our role is to build teams that will continue making secure choices as they face new tools, processes, and organizational pressures. The organizational capabilities we build today define how our teams will handle tomorrow&rsquo;s security challenges.</p>
<p>When data protection becomes embedded in your team&rsquo;s culture rather than imposed through policies, you&rsquo;ve created something much more valuable than just better security—you&rsquo;ve built a team that can adapt to new security challenges while maintaining the operational efficiency needed to build great products.</p>
]]></content></entry><entry><title type="html">SQL injection and XSS: what white hat hackers know about trusting user input</title><link href="https://victoria.dev/archive/sql-injection-and-xss-what-white-hat-hackers-know-about-trusting-user-input/"/><id>https://victoria.dev/archive/sql-injection-and-xss-what-white-hat-hackers-know-about-trusting-user-input/</id><author><name>Victoria Drake</name></author><published>2019-09-02T09:01:23-04:00</published><updated>2019-09-02T09:01:23-04:00</updated><content type="html"><![CDATA[<p>Software developers have a lot on their minds. There are are myriad of questions to ask when it comes to creating a website or application: <em>What technologies will we use? How will the architecture be set up? What functions do we need? What will the UI look like?</em> Especially in a software market where shipping new apps seems more like a race for reputation than a well-considered process, one of the most important questions often falls to the bottom of the &ldquo;Urgent&rdquo; column: how will our product be secured?</p>
<p>If you&rsquo;re using a robust, open-source framework for building your product (and if one is applicable and available, why wouldn&rsquo;t you?) then some basic security concerns, like CSRF tokens and password encryption, may already be handled for you. Still, fast-moving developers would be well served to brush up on their knowledge of common threats and pitfalls, if only to avoid some embarrass
ing rookie mistakes. Usually, the weakest point in the security of your software is <em>you.</em></p>
<p>I&rsquo;ve recently become more interested in information security in general, and practicing ethical hacking in particular. An ethical hacker, sometimes called &ldquo;white hat&rdquo; hacker, and sometimes just &ldquo;hacker,&rdquo; is someone who searches for possible security vulnerabilities and responsibly (privately) reports them to project owners. By contrast, a malicious or &ldquo;black hat&rdquo; hacker, also called a &ldquo;cracker,&rdquo; is someone who exploits these vulnerabilities for amusement or personal gain. Both white hat and black hat hackers might use the same tools and resources, and generally try to get into places they aren&rsquo;t supposed to be; however, white hats do this with permission, and with the intention of fortifying defences instead of destroying them. Black hats are the bad guys.</p>
<p>When it comes to learning how to find security vulnerabilities, it should come as no surprise that I&rsquo;ve been devouring whatever information I can get my hands on; this post is a distillation of some key areas that are specifically helpful to developers when handling user input. These lessons have been collectively gleaned from these excellent resources:</p>
<ul>
<li>The <a href="https://owasp.org/">Open Web Application Security Project</a> guides</li>
<li>The Hacker101 playlist from <a href="https://www.youtube.com/channel/UCsgzmECky2Q9lQMWzDwMhYw/">HackerOne&rsquo;s YouTube channel</a></li>
<li><a href="https://leanpub.com/web-hacking-101">Web Hacking 101</a> by Peter Yaworski</li>
<li>The <a href="https://www.youtube.com/channel/UC9-y-6csu5WGm29I7JiwpnA">Computerphile</a> YouTube channel</li>
<li>Videos featuring Jason Haddix (<a href="https://github.com/jhaddix/">@jhaddix</a>) and Tom Hudson (<a href="https://github.com/tomnomnom/">@tomnomnom</a>) (two accomplished ethical hackers with different, but both effective, methodologies)</li>
</ul>
<p>You may be familiar with the catchphrase, &ldquo;sanitize your inputs!&rdquo; However, as I hope this post demonstrates, developing an application with robust security isn&rsquo;t quite so straightforward. I suggest an alternate phrase: pay attention to your inputs. Let&rsquo;s elaborate by examining the most common attacks that take advantage of vulnerabilities in this area: SQL injection and cross site scripting.</p>
<h2 id="sql-injection-attacks">SQL injection attacks</h2>
<p>If you&rsquo;re not yet familiar with SQL (Structured Query Language) injection attacks, or SQLi, here is a great <a href="https://www.youtube.com/watch?v=_jKylhJtPmI">explain-like-I&rsquo;m-five video on SQLi</a>. You may already know of this attack from <a href="https://xkcd.com/327/">xkcd&rsquo;s Little Bobby Tables</a>. Essentially, malicious actors may be able to send SQL commands that affect your application through some input on your site, like a search box that pulls results from your database. Sites coded in PHP can be especially susceptible to these, and a successful SQL attack can be devastating for software that relies on a database (as in, your Users table is now a pot of petunias).</p>
<figure class="center"><img src="/archive/sql-injection-and-xss-what-white-hat-hackers-know-about-trusting-user-input/sqli.png"
    alt="A monitor with an SQL Select command that gets all your base"><figcaption>
      <p>You have no chance to survive make your time.</p>
    </figcaption>
</figure>

<p>You can test your own site to see if you&rsquo;re susceptible to this kind of attack. (Please only test sites that you own, since running SQL injections where you don&rsquo;t have permission to be doing so is, possibly, illegal in your locality; and definitely, universally, not very funny.) The following payloads can be used to test inputs:</p>
<ul>
<li><code>' OR 1='1</code> evaluates to a constant true, and when successful, returns all rows in the table.</li>
<li><code>' AND 0='1</code> evaluates to a constant false, and when successful, returns no rows.</li>
</ul>
<p><a href="https://www.youtube.com/watch?v=ciNHn38EyRc">This video demonstrates the above tests</a>, and does a great job of showing how impactful an SQL injection attack can be.</p>
<p>Thankfully, there are ways to mitigate SQL injection attacks, and they all boil down to one basic concept: don&rsquo;t trust user input.</p>
<h2 id="sql-injection-mitigation">SQL injection mitigation</h2>
<p>In order to effectively mitigate SQL injections, developers must prevent users from being able to successfully submit raw SQL commands to any part of the site.</p>
<p>Some frameworks will do most of the heavy lifting for you. For example, Django implements the concept of <a href="https://en.wikipedia.org/wiki/Object-relational_mapping">Object-Relational Mapping</a>, or ORM, with its use of <a href="https://docs.djangoproject.com/en/2.2/topics/db/queries/">QuerySets</a>. We can think of these as wrapper functions that help your application query the database using pre-defined methods that avoid the use of raw SQL.</p>
<p>Being able to use a framework, however, is never a guarantee. When dealing directly with a database, there are other methods we can use to safely abstract our SQL queries from user input, though they vary in efficacy. These are, by order of most to least preferred, and with links to relevant examples:</p>
<ol>
<li>Prepared statements with variable binding (or <a href="https://cheatsheetseries.owasp.org/cheatsheets/Query_Parameterization_Cheat_Sheet.html">parameterized queries</a>),</li>
<li><a href="https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html#defense-option-2-stored-procedures">Stored procedures</a>; and</li>
<li><a href="https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html#defense-option-3-whitelist-input-validation">Whitelisting</a> or <a href="https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html#defense-option-4-escaping-all-user-supplied-input">escaping</a> user input.</li>
</ol>
<p>If you want to implement the above techniques, the linked cheatsheets are a great starting point for digging deeper. Suffice to say, the use of these techniques to obtain data instead of using raw SQL queries helps to minimize the chances that SQL will be processed by any part of your application that takes input from users, thus mitigating SQL injection attacks.</p>
<p>The battle, however, is only half won&hellip;</p>
<h2 id="cross-site-scripting-xss-attacks">Cross Site Scripting (XSS) attacks</h2>
<p>If you&rsquo;re a malicious coder, JavaScript is pretty much your best friend. The right commands will do anything a legitimate user could do (and even some things they aren&rsquo;t supposed to be able to) on a web page, sometimes without any interaction on the part of an actual user. <a href="https://en.wikipedia.org/wiki/Cross-site_scripting">Cross Site Scripting</a> attacks, or XSS, occur when JavaScript code is injected into a web page and changes that page&rsquo;s behavior. Its effects can range from prank nuisance occurrences to more severe authentication bypasses or credential stealing.</p>
<figure><img src="/archive/sql-injection-and-xss-what-white-hat-hackers-know-about-trusting-user-input/xss.png"
    alt="An HTML dance party with a little JS cutting in"><figcaption>
      <p>The annual DOM dance-off receives an unexpected guest);</p>
    </figcaption>
</figure>

<p>XSS can occur on the server or on the client side, and generally comes in three flavors: DOM (<a href="https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction">Document Object Model</a>) based, stored, and reflected XSS. The differences amount to where the attack payload is injected into the application.</p>
<h3 id="dom-based-xss">DOM-based XSS</h3>
<p><a href="https://owasp.org/www-project-web-security-testing-guide/v41/4-Web_Application_Security_Testing/11-Client_Side_Testing/01-Testing_for_DOM-based_Cross_Site_Scripting">DOM-based XSS</a> occurs when a JavaScript payload affects the structure, behavior, or content of the web page the user has loaded in their browser. These are most commonly executed through modified URLs, such as in phishing.</p>
<p>To see how easy it would be for injected JavaScript to manipulate a page, we can create a working example with an HTML web page. Try creating a file on your local system called <code>xss-test.html</code> (or whatever you like) with the following HTML and JavaScript code:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#f92672">html</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">head</span>&gt;
</span></span><span style="display:flex;"><span>        &lt;<span style="color:#f92672">title</span>&gt;My XSS Example&lt;/<span style="color:#f92672">title</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">head</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">body</span>&gt;
</span></span><span style="display:flex;"><span>        &lt;<span style="color:#f92672">h1</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;greeting&#34;</span>&gt;Hello there!&lt;/<span style="color:#f92672">h1</span>&gt;
</span></span><span style="display:flex;"><span>            &lt;<span style="color:#f92672">script</span>&gt;
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">name</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">URLSearchParams</span>(document.<span style="color:#a6e22e">location</span>.<span style="color:#a6e22e">search</span>).<span style="color:#a6e22e">get</span>(<span style="color:#e6db74">&#39;name&#39;</span>);
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">name</span> <span style="color:#f92672">!==</span> <span style="color:#e6db74">&#39;null&#39;</span>) {
</span></span><span style="display:flex;"><span>                    document.<span style="color:#a6e22e">getElementById</span>(<span style="color:#e6db74">&#39;greeting&#39;</span>).<span style="color:#a6e22e">innerHTML</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;Hello &#39;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">name</span> <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39;!&#39;</span>;
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>            &lt;/<span style="color:#f92672">script</span>&gt;
</span></span><span style="display:flex;"><span>        &lt;/<span style="color:#f92672">h1</span>&gt;
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">html</span>&gt;
</span></span></code></pre></div><p>This web page will display the title &ldquo;Hello there!&rdquo; unless it receives a <a href="https://en.wikipedia.org/wiki/Query_string">URL parameter from a query string</a> with a value for <code>name</code>. To see the script work, open the page in a browser with an appended URL parameter, like so:</p>
<p><code>file:///path/to/file/xss-test.html?name=Victoria</code></p>
<p>Fun, right? Our insecure (in the safety sense, not the emotional one) page takes the URL parameter value for <code>name</code> and displays it in the DOM. The page is expecting the value to be a nice friendly string, but what if we change it to something else? Since the page is owned by us and only exists on our local system, we can test it all we like. What happens if we change the <code>name</code> parameter to, say, <code>&lt;img+src+onerror=alert(&quot;pwned&quot;)&gt;</code>?</p>
<p><img src="pwned.png#screenshot" alt="A screenshot of the XSS page example"></p>
<p>This is just one example that demonstrates how an XSS attack could be executed. Funny pop-up alerts may be amusing, but JavaScript can do a lot of harm, including helping malicious attackers steal passwords and personal information.</p>
<h3 id="stored-and-reflected-xss">Stored and reflected XSS</h3>
<p><a href="https://en.wikipedia.org/wiki/Cross-site_scripting#Persistent_(or_stored)">Stored XSS</a> occurs when the attack payload is stored on the server, such as in a database. The attack affects a victim whenever that stored data is retrieved and rendered in the browser. For example, instead of using a URL query string, an attacker might update their profile page on a social site to include a hidden script in, say, their &ldquo;About Me&rdquo; section. The script, improperly stored on the site&rsquo;s server, would successfully execute at a later time when another user views the attacker&rsquo;s profile.</p>
<p>One of the most famous examples of this is the <a href="https://en.wikipedia.org/wiki/Samy_(computer_worm)">Samy worm</a> that all but took over MySpace in 2005. It propagated by sending HTTP requests that replicated it onto a victim&rsquo;s profile page whenever an infected profile was viewed. Within just 20 hours, it had spread to over a million users.</p>
<p><a href="https://en.wikipedia.org/wiki/Cross-site_scripting#Non-persistent_(reflected)">Reflected XSS</a> similarly occurs when the injected payload travels to the server, however, the malicious code does not end up stored in a database. It is instead immediately returned to the browser by the web application. An attack like this might be executed by luring the victim to click a malicious link that sends a request to the vulnerable website&rsquo;s server. The server would then send a response to the attacker as well as the victim, which may result in the attacker being able to obtain passwords, or perpetrate actions that appear to originate from the victim.</p>
<h2 id="xss-attack-mitigation">XSS attack mitigation</h2>
<p>In all of these cases, XSS attacks can be mitigated with two key strategies: validating form fields, and avoiding the direct injection of user input on the web page.</p>
<h3 id="validating-form-fields">Validating form fields</h3>
<p>Frameworks can again help us out when it comes to making sure that user-submitted forms are on the up-and-up. One example is <a href="https://docs.djangoproject.com/en/2.2/ref/forms/fields/#built-in-field-classes">Django&rsquo;s built-in <code>Field</code> classes</a>, which provide fields that validate to some commonly used types and also specify sane defaults. Django&rsquo;s <code>EmailField</code>, for instance, uses a set of rules to determine if the input provided is a valid email. If the submitted string has characters in it that are not typically present in email addresses, or if it doesn&rsquo;t imitate the common format of an email address, then Django won&rsquo;t consider the field valid and the form will not be submitted.</p>
<p>If relying on a framework isn&rsquo;t an option, we can implement our own input validation. This can be accomplished with a few different techniques, including <a href="https://en.wikipedia.org/wiki/Type_conversion">type conversion</a>, for example, ensuring that a number is of type <code>int()</code>; checking minimum and maximum range values for numbers and lengths for strings; using a pre-defined array of choices that avoids arbitrary input, for example, months of the year; and checking data against strict <a href="https://en.wikipedia.org/wiki/Regular_expression">regular expressions</a>.</p>
<p>Thankfully, we needn&rsquo;t start from scratch. Open source resources are available to help, such as the <a href="https://owasp.org/www-community/OWASP_Validation_Regex_Repository">OWASP Validation Regex Repository</a>, which provides patterns to match against for some common forms of data. Many programming languages offer validation libraries specific to their syntax, and we can find <a href="https://github.com/search?q=validation+library">plenty of these on GitHub</a>. Additionally, the <a href="https://cheatsheetseries.owasp.org/cheatsheets/XSS_Filter_Evasion_Cheat_Sheet.html">XSS Filter Evasion Cheat Sheet</a> has a couple suggestions for test payloads we can use to test our existing applications.</p>
<p>While it may seem tedious, properly implemented input validation can protect our application from being susceptible to XSS.</p>
<h3 id="avoiding-direct-injection">Avoiding direct injection</h3>
<p>Elements of an application that directly return user input to the browser may not, on a casual inspection, be obvious. We can determine areas of our application that may be at risk by exploring a few questions:</p>
<ul>
<li>How does data flow through our application?</li>
<li>What does a user expect to happen when they interact with this input?</li>
<li>Where on our page does data appear? Does it become embedded in a string or an attribute?</li>
</ul>
<p>Here are some sample payloads that we can play with in order to test inputs on our site (again, only our own site!) courtesy of <a href="https://www.hacker101.com/">Hacker101</a>. The successful execution of any of these samples can indicate a possible XSS vulnerability due to direct injection.</p>
<ul>
<li><code>&quot;&gt;&lt;h1&gt;test&lt;/h1&gt;</code></li>
<li><code>'+alert(1)+'</code></li>
<li><code>&quot;onmouserover=&quot;alert(1)</code></li>
<li><code>http://&quot;onmouseover=&quot;alert(1)</code></li>
</ul>
<p>As a general rule, if you are able to design around directly injecting input, do so. Alternatively, be sure to completely understand the effect of the methods you choose; for example, using <code>innerText</code> instead of <code>innerHTML</code> in JavaScript will ensure that content will be set as plain text instead of (potentially vulnerable) HTML.</p>
<h2 id="pay-attention-to-your-inputs">Pay attention to your inputs</h2>
<p>Software developers are at a marked disadvantage when it comes to competing with black hat, or malicious, hackers. For all the work we do to secure each and every input that could potentially compromise our application, an attacker need only find the one we missed. It&rsquo;s like installing deadbolts on all the doors, but leaving a window open!</p>
<p>By learning to think along the same lines as an attacker, however, we can better prepare our software to stand up against bad actors. Exciting as it may be to ship features as quickly as possible, we&rsquo;ll avoid racking up a lot of security debt if we take the time beforehand to think through our application&rsquo;s flow, follow the data, and pay attention to our inputs.</p>
]]></content></entry><entry><title type="html">How to set up OpenVPN on AWS EC2 and fix DNS leaks on Ubuntu 18.04 LTS</title><link href="https://victoria.dev/archive/how-to-set-up-openvpn-on-aws-ec2-and-fix-dns-leaks-on-ubuntu-18.04-lts/"/><id>https://victoria.dev/archive/how-to-set-up-openvpn-on-aws-ec2-and-fix-dns-leaks-on-ubuntu-18.04-lts/</id><author><name>Victoria Drake</name></author><published>2019-08-26T09:01:23-04:00</published><updated>2019-08-26T09:01:23-04:00</updated><content type="html"><![CDATA[<p>There&rsquo;s no better way to strive for maximum privacy than a VPN service you control, configure, and maintain yourself. Here&rsquo;s a step-by-step tutorial for <a href="#set-up-openvpn-on-aws-ec2">setting up your own OpenVPN on AWS EC2</a>, and <a href="#what-a-dns-leak-looks-like">how to check for and fix DNS leaks</a>.</p>
<p>For a VPN that also blocks ads and trackers, you can <a href="/blog/set-up-a-pi-hole-vpn-on-an-aws-lightsail-instance/">set up a Pi-hole VPN on an AWS Lightsail instance</a> instead.</p>
<h2 id="set-up-openvpn-on-aws-ec2">Set up OpenVPN on AWS EC2</h2>
<p>This post will cover how to set up the <a href="https://aws.amazon.com/marketplace/pp/B00MI40CAE/">OpenVPN Access Server</a> product on AWS Marketplace, running on an <a href="https://aws.amazon.com/ec2/">Amazon EC2 instance</a>. Then, you&rsquo;ll look at how to fix a <a href="https://gitlab.gnome.org/GNOME/NetworkManager-openvpn/issues/10">known NetworkManager bug in Ubuntu 18.04 that might cause DNS leaks</a>. The whole process should take about fifteen minutes, so grab a ☕ and let&rsquo;s be configuration superheroes.</p>
<p><em>Note: IDs and IP addresses shown for demonstration in this tutorial are invalid.</em></p>
<h3 id="1-launch-the-openvpn-access-server-on-aws-marketplace">1. Launch the OpenVPN Access Server on AWS Marketplace</h3>
<p>The <a href="https://aws.amazon.com/marketplace/pp/B00MI40CAE">OpenVPN Access Server</a> is available on AWS Marketplace. The Bring Your Own License (BYOL) model doesn&rsquo;t actually require a license for up to two connected devices; to connect more clients, you can get <a href="https://aws.amazon.com/marketplace/seller-profile/ref=srh_res_product_vendor?ie=UTF8&amp;id=aac3a8a3-2823-483c-b5aa-60022894b89d">bundled billing</a> for five, ten, or twenty-five clients, or <a href="https://openvpn.net/access-server/pricing/">purchase a minimum of ten OpenVPN licenses a la carte</a> for $15/device/year. For most of us, the two free connected devices will suffice; and if using an EC2 Micro instance, your set up will be <a href="https://aws.amazon.com/free/">AWS Free Tier eligible</a> as well.</p>
<p>Start by clicking <strong>Continue to Subscribe</strong> for the <a href="https://aws.amazon.com/marketplace/pp/B00MI40CAE">OpenVPN Access Server</a>, which will bring you to a page that looks like this:</p>
<p><img src="1-subscribe.jpg#screenshot" alt="Subscription details page for OpenVPN Access Server"></p>
<p>Click <strong>Continue to Configuration</strong>.</p>
<p><img src="2-configure.jpg#screenshot" alt="Configure this software page for OpenVPN Access Server"></p>
<p>You may notice that the EC2 instance type in the right side bar (and consequently, the Monthly Estimate) isn&rsquo;t the one you want - that&rsquo;s okay, you can change it soon. Just ensure that the <strong>Region</strong> chosen is where you want the instance to be located. Generally, the closer it is to the physical location of your client (your laptop, in this case), the faster your VPN will be. Click <strong>Continue to Launch</strong>.</p>
<p><img src="3-launch.jpg#screenshot" alt="Launch this software page"></p>
<p>On this page, you&rsquo;ll change three things:</p>
<h4 id="1-the-ec2-instance-type">1. The EC2 Instance type</h4>
<p>Different types of EC2 (Elastic Compute Cloud) instances will offer you different levels of computing power. If you plan to use your instance for something more than just this VPN, you may want to choose something with higher memory or storage capacity, depending on how you plan to use it. You can view each instance offering on the <a href="https://aws.amazon.com/ec2/instance-types/">Amazon EC2 Instance Types page</a>.</p>
<p>For simple VPN use, the <code>t2.nano</code> or <code>t2.micro</code> instances are likely sufficient. Only the Micro instance is Free Tier eligible.</p>
<h4 id="2-the-security-group-settings">2. The Security Group settings</h4>
<p>A <a href="https://docs.aws.amazon.com/vpc/latest/userguide/VPC_SecurityGroups.html">Security Group</a> is a profile, or collection of settings, that Amazon uses to control access to your instance. If you&rsquo;ve set up other AWS products before, you may already have some groups with their own rules defined. You should be careful to understand the reasons for your Security Group settings, as these define how public or private your instance is, and consequently, who has access to it.</p>
<p>If you click <strong>Create New Based on Seller Settings</strong>, the OpenVPN server defines some recommended settings for a default Security Group.</p>
<p><img src="4-security-group.jpg#screenshot" alt="Security group settings"></p>
<p>The default recommended settings are all <code>0.0.0.0/0</code> for TCP ports 22, 943, 443, and 945, and UDP port 1194. OpenVPN offers an <a href="https://openvpn.net/vpn-server-resources/amazon-web-services-ec2-byol-appliance-quick-start-guide/#Instance_Launch_Options">explanation of how the ports are used</a> on their website. With the default settings, all these ports are left open to support various features of the OpenVPN server. You may wish to restrict access to these ports to a specific IP address or block of addresses (like that of your own ISP) to increase the security of your instance. However, if your IP address frequently changes (like when you travel and connect to a different WiFi network), restricting the ports may not be as helpful as you hope.</p>
<p>In any case, your instance will require SSH keys to connect to, and the OpenVPN server will be password protected. Unless you have other specific security goals, it&rsquo;s fine to accept the default settings for now.</p>
<p>Let&rsquo;s give the Security Group a name and brief description, so you know what it&rsquo;s for. Then click <strong>Save</strong>.</p>
<h4 id="3-the-key-pair-settings">3. The Key Pair settings</h4>
<p>The aforementioned SSH keys are access credentials that you&rsquo;ll use to connect to your instance. You can <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html#having-ec2-create-your-key-pair">create a key pair</a> in this section, or you can choose a key pair you may already be using with AWS.</p>
<p><img src="5-keys.jpg#screenshot" alt="Key Pair Settings link"></p>
<p>To create a new set of access credentials, click <strong>Create a key pair in EC2</strong> to open a new window. Then, click the <strong>Create Key Pair</strong> blue button. Once you give your key pair a name, it will be created and the private key will automatically download to your machine. It&rsquo;s a file ending with the extension <code>.pem</code>. Store this key in a secure place on your computer. You&rsquo;ll need to refer to it when you connect to your new EC2 instance.</p>
<p>You can return to the previous window to select the key pair you just created. If it doesn&rsquo;t show up, hit the little &ldquo;refresh&rdquo; icon next to the drop-down. Once it&rsquo;s selected, hit the shiny yellow <strong>Launch</strong> button.</p>
<p>You should see a message like this:</p>
<p><img src="6-launched.jpg#screenshot" alt="Launch success message"></p>
<p>Great stuff! Now that your instance exists, let&rsquo;s make sure you can access it and start up your VPN. For a shortcut to the next step, click on the &ldquo;EC2 Console&rdquo; link in the success message.</p>
<h3 id="2-associate-an-elastic-ip">2. Associate an Elastic IP</h3>
<p>Amazon&rsquo;s <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/elastic-ip-addresses-eip.html">Elastic IP Addresses</a> provides you with a public IPv4 address controlled by your account, unlike the public IP address tied to your EC2 instance. It&rsquo;s considered a best practice to create one and associate it with your VPN instance. If anything should go wrong with your instance, or if you want to use a new instance for your VPN in the future, the Elastic IP can be disassociated from the current instance and reassociated with your new one. This makes the transition seamless for your connected clients. Think of the Elastic IP like a web domain address that you register - you can point it at whatever you choose.</p>
<p>We can create a new Elastic IP address on the Amazon EC2 Console. If you clicked the link from the success message above, we&rsquo;re already there.</p>
<p><img src="7-ec2.jpg#screenshot" alt="EC2 console"></p>
<p>If you have more than one instance, take note of the Instance ID of the one you&rsquo;ve just launched.</p>
<p>In the left sidebar under <strong>Network &amp; Security</strong>, choose <strong>Elastic IPs</strong>. Then click the blue <strong>Allocate new address</strong> button.</p>
<p><img src="8-elasticip.jpg#screenshot" alt="Allocate new address page"></p>
<p>Choose <strong>Amazon Pool,</strong> then click <strong>Allocate</strong>.</p>
<p><img src="9-elasticip.jpg#screenshot" alt="Allocate elastic IP success message"></p>
<p>Success! Click <strong>Close</strong> to return to the Elastic IP console.</p>
<p><img src="10-associateip.jpg#screenshot" alt="Associate elastic IP"></p>
<p>Now that you have an Elastic IP, let&rsquo;s associate it with your instance. Select the IP address, then click <strong>Actions,</strong> and choose <strong>Associate address</strong>.</p>
<p><img src="11-associateip.jpg#screenshot" alt="Associate elastic IP with instance"></p>
<p>Ensure the <strong>Instance</strong> option is selected, then click the drop-down menu. You should see your EC2 instance ID there. Select it, then click <strong>Associate</strong>.</p>
<p><img src="12-associateip.jpg#screenshot" alt="Associate elastic IP success message"></p>
<p>Success! Now that you&rsquo;ll be able to access your VPN instance, let&rsquo;s get your VPN service up and running.</p>
<h3 id="3-initialize-openvpn-on-the-ec2-server">3. Initialize OpenVPN on the EC2 server</h3>
<p>First, you&rsquo;ll need to connect to the EC2 instance via your terminal. You&rsquo;ll use the private key you created earlier.</p>
<p>Open a new terminal window and navigate to the directory containing the private key <code>.pem</code> file. You&rsquo;ll need to set its permissions with:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>sudo chmod <span style="color:#ae81ff">400</span> &lt;name&gt;.pem
</span></span></code></pre></div><p>Be sure to substitute <code>&lt;name&gt;</code> with the name of your key.</p>
<p>This sets the file permissions to <code>-r--------</code> so that it can only be read by the user (you). It may help to protect the private key from read and write operations by other users, but more importantly, will prevent AWS from throwing an error when you try to connect to your instance.</p>
<p>We can now do just that by running:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>ssh -i &lt;name&gt;.pem openvpnas@&lt;elastic ip&gt;
</span></span></code></pre></div><p>The user <code>openvpnas</code> is set up by the OpenVPN Access Server to allow you to connect to your instance. Replace <code>&lt;elastic ip&gt;</code> with the Elastic IP address you just associated.</p>
<p>We may get a message saying that the authenticity of your host can&rsquo;t be established. As long as you&rsquo;ve typed the Elastic IP correctly, go ahead and answer <strong>yes</strong> to the prompt.</p>
<p>Upon the initial connection to the OpenVPN instance, a set up wizard called <strong>Initial Configuration Tool</strong> should automatically run. (If, for some reason, it doesn&rsquo;t, or you panic-mashed a button, you can restart it with <code>sudo ovpn-init –ec2</code>.) You&rsquo;ll be asked to accept the agreement, then the wizard will help to walk you through some configuration settings for your VPN server.</p>
<p>You may generally accept the default settings, however, there are a couple questions you may like to answer knowledgeably. They are:</p>
<p><strong>Should client traffic be routed by default through the VPN?</strong></p>
<p><strong>Should client DNS traffic be routed by default through the VPN?</strong></p>
<p>These answers depend on your privacy goals for your VPN.</p>
<p>When asked for your <strong>OpenVPN-AS license key</strong>, you can leave it blank to use the VPN with up to two clients. If you&rsquo;ve purchased a key, enter it here.</p>
<p>Once the configuration wizard finishes running, you should see the message &ldquo;Initial Configuration Complete!&rdquo; Before you move on, you should set a password for your server&rsquo;s administration account. To do this, run:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>sudo passwd openvpn
</span></span></code></pre></div><p>Then enter your chosen password twice. Now we&rsquo;re ready to get connected!</p>
<p>To close the SSH connection, type <code>exit</code>.</p>
<h3 id="4-connect-the-client-to-the-vpn">4. Connect the client to the VPN</h3>
<p>To connect your client (in this case, your laptop) to the VPN and start reaping the benefits, you&rsquo;ll need to do two things; first, obtain your connection profile; second, install the <code>openvpn</code> daemon.</p>
<h4 id="1-get-your-ovpn-connection-profile">1. Get your <code>.ovpn</code> connection profile</h4>
<p>You&rsquo;ll need to download a connection profile; this is like a personal configuration file with information, including keys, that the VPN server will need to allow your connection. You can do this by logging in with the password you just set at your Elastic IP address, port 943. This looks like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>https://&lt;elastic ip&gt;:943/
</span></span></code></pre></div><p>The <code>https</code> part is important; without it, the instance won&rsquo;t send any data.</p>
<p>When you go to this URL, you may see a page warning you that this site&rsquo;s certificate issuer is unknown or invalid. As long as you&rsquo;ve typed your Elastic IP correctly, it&rsquo;s safe to proceed. If you&rsquo;re using Firefox, click <strong>Advanced</strong>, and then <strong>Accept the Risk and Continue</strong>. In Chrome, click <strong>Advanced</strong>, then <strong>Proceed</strong> to the elastic IP.</p>
<p><img src="13-warning.jpg#screenshot" alt="Security warning page"></p>
<p>Log in with the username <code>openvpn</code> and the password you just set. You&rsquo;ll now be presented with a link to download your user-locked connection profile:</p>
<p><img src="14-profile.jpg#screenshot" alt="Connection profile download page"></p>
<p>When you click the link, a file named <code>client.ovpn</code> will download.</p>
<h4 id="2-install-and-start-openvpn-on-your-ubuntu-1804-client">2. Install and start <code>openvpn</code> on your Ubuntu 18.04 client</h4>
<p>The <code>openvpn</code> daemon will allow your client to connect to your VPN server. It can be installed through the default Ubuntu repositories. Run:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>sudo apt install openvpn
</span></span></code></pre></div><p>In order for OpenVPN to automatically start when you boot up your computer, you&rsquo;ll need to rename and move the connection profile file. I suggest using a <a href="https://en.wikipedia.org/wiki/Symbolic_link">symlink</a> to accomplish this, as it leaves your original file more easily accessible for editing, and allows you to store it in any directory you choose. You can create a symlink by running this command in the directory where your file is located:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>sudo ln -s client.ovpn /etc/openvpn/&lt;name&gt;.conf
</span></span></code></pre></div><p>This creates a symbolic link for the connection profile in the appropriate folder for <code>systemd</code> to find it. The <code>&lt;name&gt;</code> can be anything. When the Linux kernel has booted, <code>systemd</code> is used to initialize the services and daemons that the user has set up to run; one of these will now be OpenVPN. Renaming the file with the extension <code>.conf</code> will let the <code>openvpn</code> daemon know to use it as your connection file.</p>
<p>For now, you can manually start and connect to OpenVPN by running:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>sudo openvpn --config client.ovpn
</span></span></code></pre></div><p>You&rsquo;ll be asked for a username and password, which will be the same credentials you used before. Once the service finishes starting up, you&rsquo;ll see &ldquo;Initialization Sequence Complete.&rdquo; If you now visit <a href="https://www.dnsleaktest.com/">the DNS leak test website</a>, you should see the Elastic IP and the location of your EC2 server. Yay!</p>
<p>If you&rsquo;re on a later version of Ubuntu, you may check for DNS leaks by clicking on one of the test buttons. If all the ISPs shown are Amazon and none are your own service provider&rsquo;s, congratulations! No leaks! You can move on to <a href="#3-set-up-openvpn-as-networkmanager-system-connection">Step 3 in the second section</a> below, after which, you&rsquo;ll be finished.</p>
<p>If you&rsquo;re using Ubuntu 18.04 LTS, however, we&rsquo;re not yet done.</p>
<h2 id="what-a-dns-leak-looks-like">What a DNS leak looks like</h2>
<p>Sites like <a href="https://dnsleaktest.com/">the DNS leak test website</a> can help you check your configuration and see if the Internet knows more about your location than you&rsquo;d like. On the main page you&rsquo;ll see a big hello, your IP address, and your location, so far as can be determined.</p>
<p>If you have a DNS leak, you can see what it looks like by clicking on one of the test buttons on the <a href="https://www.dnsleaktest.com/">the DNS leak test page</a>. When you do, you&rsquo;ll see not only your Amazon.com IP addresses, but also your own ISP and location.</p>
<p>You can also see the leak by running <code>systemd-resolve --status</code> in your terminal. Your results will contain two lines under different interfaces that both have entries for DNS Servers. It&rsquo;ll look something like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>Link <span style="color:#ae81ff">7</span> <span style="color:#f92672">(</span>tun0<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>      Current Scopes: DNS
</span></span><span style="display:flex;"><span>       LLMNR setting: yes
</span></span><span style="display:flex;"><span>MulticastDNS setting: no
</span></span><span style="display:flex;"><span>      DNSSEC setting: no
</span></span><span style="display:flex;"><span>    DNSSEC supported: no
</span></span><span style="display:flex;"><span>         DNS Servers: 172.31.0.2
</span></span><span style="display:flex;"><span>          DNS Domain: ~.
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Link <span style="color:#ae81ff">3</span> <span style="color:#f92672">(</span>wlp4s0<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>      Current Scopes: none
</span></span><span style="display:flex;"><span>       LLMNR setting: yes
</span></span><span style="display:flex;"><span>MulticastDNS setting: no
</span></span><span style="display:flex;"><span>      DNSSEC setting: no
</span></span><span style="display:flex;"><span>    DNSSEC supported: no
</span></span><span style="display:flex;"><span>         DNS Servers: 192.168.0.1
</span></span><span style="display:flex;"><span>          DNS Domain: ~.
</span></span></code></pre></div><p>The <a href="https://unix.stackexchange.com/questions/434916/how-to-fix-openvpn-dns-leak">DNS leak problem in Ubuntu 18.04</a> stems from Ubuntu&rsquo;s DNS resolver, <code>systemd-resolved</code>, failing to properly handle your OpenVPN configuration. In order to try and be a good, efficient DNS resolver, <code>systemd-resolved</code> will send DNS lookup requests in parallel to each interface that has a DNS server configuration, and then utilizes the fastest response. In your case, you only want to use your VPN&rsquo;s DNS servers. Sorry, <code>systemd-resolved</code>. You tried.</p>
<h2 id="how-to-fix-openvpn-dns-leak-on-ubuntu-1804">How to fix OpenVPN DNS leak on Ubuntu 18.04</h2>
<p>Luckily, there is a fix that you can implement. You&rsquo;ll need to install a few helpers from the Ubuntu repositories, update your configuration file, then set up OpenVPN using NetworkManager. Let&rsquo;s do it!</p>
<h3 id="1-install-some-helpers">1. Install some helpers</h3>
<p>To properly integrate OpenVPN with <code>systemd-resolved</code>, you&rsquo;ll need a bit more help. In a terminal, run:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>sudo apt install -y openvpn-systemd-resolved network-manager-openvpn network-manager-openvpn-gnome
</span></span></code></pre></div><p>This will install a helper script that integrates OpenVPN and <code>systemd-resolved</code>, a NetworkManager plugin for OpenVPN, and its GUI counterpart for GNOME desktop environment.</p>
<h3 id="2-add-dns-implementation-to-your-connection-profile">2. Add DNS implementation to your connection profile</h3>
<p>You&rsquo;ll need to edit the connection profile file you downloaded earlier. Since it&rsquo;s symbolically linked, you can accomplish this by changing the <code>.ovpn</code> file, wherever it&rsquo;s stored. Run <code>vim &lt;name&gt;.ovpn</code> to open it in Vim, then add the following lines at the bottom. Explanation in the comments:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span><span style="color:#75715e"># Allow OpenVPN to call user-defined scripts</span>
</span></span><span style="display:flex;"><span>script-security <span style="color:#ae81ff">2</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Tell systemd-resolved to send all DNS queries over the VPN</span>
</span></span><span style="display:flex;"><span>dhcp-option DOMAIN-ROUTE .
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Use the update-systemd-resolved script when TUN/TAP device is opened,</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># and also run the script on restarts and before the TUN/TAP device is closed</span>
</span></span><span style="display:flex;"><span>up /etc/openvpn/update-systemd-resolved
</span></span><span style="display:flex;"><span>up-restart
</span></span><span style="display:flex;"><span>down /etc/openvpn/update-systemd-resolved
</span></span><span style="display:flex;"><span>down-pre
</span></span></code></pre></div><p>For the full list of OpenVPN options, see <a href="https://openvpn.net/community-resources/reference-manual-for-openvpn-2-1/">OpenVPN Scripting and Environment Variables</a>. You may also like <a href="https://en.wikipedia.org/wiki/TUN/TAP">more information about TUN/TAP</a>.</p>
<h3 id="3-set-up-openvpn-as-networkmanager-system-connection">3. Set up OpenVPN as NetworkManager system connection</h3>
<p>Use the GUI to set up your VPN with NetworkManager. Open up Network Settings, which should look something like this:</p>
<p><img src="15-networksettings.png#screenshot" alt="Network Settings window on Ubuntu 18.04"></p>
<p>Then click the plus sign (<strong>+</strong>) button. On the window that pops up, counterintuitively, choose <strong>Import from file&hellip;</strong> instead of the OpenVPN option.</p>
<p><img src="16-importvpn.jpg#screenshot" alt="Add VPN window"></p>
<p>Navigate to, and then select, your <code>.ovpn</code> file. You should now see something like this:</p>
<p><img src="17-vpnsettings.png#screenshot" alt="The filled VPN connection settings"></p>
<p>Add your username and password for the server (<code>openvpn</code> and the password you set in <a href="#3-initialize-openvpn-on-the-ec2-server">the first section&rsquo;s Step 3</a>), and your user key password (the same one again, if you&rsquo;ve followed this tutorial), then click the &ldquo;Add&rdquo; button.</p>
<h3 id="4-edit-your-openvpn-networkmanager-configuration">4. Edit your OpenVPN NetworkManager configuration</h3>
<p>Nearly there! Now that you&rsquo;ve added the VPN as a NetworkManager connection, you&rsquo;ll need to make a quick change to it. You can see a list of NetworkManager connections by running:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>ls -la /etc/NetworkManager/system-connections/*
</span></span></code></pre></div><p>The one for your VPN is probably called <code>openvpn</code>, so let&rsquo;s edit it by running:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>sudo vim /etc/NetworkManager/system-connections/openvpn
</span></span></code></pre></div><p>Under <code>[ipv4]</code>, you&rsquo;ll need to add the line <code>dns-priority=-42</code>. It should end up looking like this:</p>
<p><img src="18-connsettings.jpg#screenshot" alt="Connection settings for ipv4"></p>
<p>Setting a negative number is a workaround that prioritizes this DNS server. The actual number is arbitrary (<code>-1</code> should also work) but I like 42. ¯\_(ツ)_/¯</p>
<h3 id="5-restart-connect-profit">5. Restart, connect, profit</h3>
<p>In a terminal, run:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>sudo service network-manager restart
</span></span></code></pre></div><p>Then in the Network Settings, click the magic button that turns on the VPN:</p>
<p><img src="19-vpnon.jpg#screenshot" alt="Network Settings window"></p>
<p>Finally, visit <a href="https://www.dnsleaktest.com/">the DNS leak test website</a> and click on <strong>Extended test</strong> to verify the fix. If everything&rsquo;s working properly, you should now see a list containing only your VPN ISP.</p>
<p><img src="20-noleaks.png#screenshot" alt="Successful DNS leak test results"></p>
<p>And we&rsquo;re done! Congratulations on rolling your very own VPN server and stopping DNS leaks with OpenVPN. Enjoy surfing in (relative) privacy. Now your only worry at the local coffeeshop is who&rsquo;s watching you surf from the seat behind you.</p>
<p>If you enjoyed this post, there&rsquo;s a lot more where it came from! I write about computing, cybersecurity, and leading great technical teams. You can subscribe below to see new posts first.</p>
<a href="https://medium.com/@victoriadotdev/subscribe" target="_blank" rel="noopener noreferrer" class="subscribe-button">
    <span class="subscribe-icon">📧</span>
    <span class="subscribe-text">Subscribe</span>
</a>

]]></content></entry><entry><title type="html">How to do twice as much with half the keystrokes using `.bashrc`</title><link href="https://victoria.dev/archive/how-to-do-twice-as-much-with-half-the-keystrokes-using-.bashrc/"/><id>https://victoria.dev/archive/how-to-do-twice-as-much-with-half-the-keystrokes-using-.bashrc/</id><author><name>Victoria Drake</name></author><published>2019-08-21T09:17:02-04:00</published><updated>2019-08-21T09:17:02-04:00</updated><content type="html"><![CDATA[<p>In my <a href="/blog/how-to-set-up-a-fresh-ubuntu-desktop-using-only-dotfiles-and-bash-scripts/">recent post about setting up Ubuntu with Bash scripts</a>, I briefly alluded to the magic of <code>.bashrc</code>. This didn&rsquo;t really do it justice, so here&rsquo;s a quick post that offers a bit more detail about what the Bash configuration file can do.</p>
<p>My current configuration hugely improves my workflow, and saves me well over 50% of the keystrokes I would have to employ without it! Let&rsquo;s look at some examples of aliases, functions, and prompt configurations that can improve our workflow by helping us be more efficient with fewer key presses.</p>
<h2 id="bash-aliases">Bash aliases</h2>
<p>A smartly written <code>.bashrc</code> can save a whole lot of keystrokes. You can take advantage of this in the literal sense by using <a href="https://www.gnu.org/software/bash/manual/html_node/Aliases.html">bash aliases</a>, or strings that expand to larger commands. For an indicative example, here is a Bash alias for copying files in the terminal:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># Always copy contents of directories (r)ecursively and explain (v) what was done</span>
</span></span><span style="display:flex;"><span>alias cp<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;cp -rv&#39;</span>
</span></span></code></pre></div><p>The <code>alias</code> command defines the string you&rsquo;ll type, followed by what that string will expand to. You can override existing commands like <code>cp</code> above. On its own, the <code>cp</code> command will only copy files, not directories, and succeeds silently. With this alias, you need not remember to pass those two flags, nor <code>cd</code> or <code>ls</code> the location of our copied file to confirm that it&rsquo;s there! Now, just those two key presses (for <code>c</code> and <code>d</code>) will do all of that for us.</p>
<p>Here are a few more <code>.bashrc</code> aliases for passing flags with common functions.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># List contents with colors for file types, (A)lmost all hidden files (without . and ..), in (C)olumns, with class indicators (F)</span>
</span></span><span style="display:flex;"><span>alias ls<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;ls --color=auto -ACF&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># List contents with colors for file types, (a)ll hidden entries (including . and ..), use (l)ong listing format, with class indicators (F)</span>
</span></span><span style="display:flex;"><span>alias ll<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;ls --color=auto -alF&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Explain (v) what was done when moving a file</span>
</span></span><span style="display:flex;"><span>alias mv<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;mv -v&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Create any non-existent (p)arent directories and explain (v) what was done</span>
</span></span><span style="display:flex;"><span>alias mkdir<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;mkdir -pv&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Always try to (c)ontinue getting a partially-downloaded file</span>
</span></span><span style="display:flex;"><span>alias wget<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;wget -c&#39;</span>
</span></span></code></pre></div><p>Aliases come in handy when you want to avoid typing long commands, too. Here are a few I use when working with Python environments:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>alias pym<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;python3 manage.py&#39;</span>
</span></span><span style="display:flex;"><span>alias mkenv<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;python3 -m venv env&#39;</span>
</span></span><span style="display:flex;"><span>alias startenv<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;source env/bin/activate &amp;&amp; which python3&#39;</span>
</span></span><span style="display:flex;"><span>alias stopenv<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;deactivate&#39;</span>
</span></span></code></pre></div><p>For further inspiration on ways Bash aliases can save time, I highly recommend <a href="https://www.digitalocean.com/community/tutorials/an-introduction-to-useful-bash-aliases-and-functions">the examples in this article</a>.</p>
<h2 id="bash-functions">Bash functions</h2>
<p>One downside of the aliases above is that they&rsquo;re rather static - they&rsquo;ll always expand to exactly the text declared. For a Bash alias that takes arguments, you&rsquo;ll need to create a function. You can do this like so:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># Show contents of the directory after changing to it</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">function</span> cd <span style="color:#f92672">()</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>    builtin cd <span style="color:#e6db74">&#34;</span>$1<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>    ls -ACF
</span></span><span style="display:flex;"><span><span style="color:#f92672">}</span>
</span></span></code></pre></div><p>I can&rsquo;t begin to tally how many times I&rsquo;ve typed <code>cd</code> and then <code>ls</code> immediately after to see the contents of the directory I&rsquo;m now in. With this function set up, it all happens with just those two letters! The function takes the first argument, <code>$1</code>, as the location to change directory to, then prints the contents of that directory in nicely formatted columns with file type indicators. The <code>builtin</code> part is necessary to get Bash to allow us to override this default command.</p>
<p>Bash functions are very useful when it comes to downloading or upgrading software, too.</p>
<h3 id="bash-function-for-downloading-extended-hugo">Bash function for downloading extended Hugo</h3>
<p>Thanks to the static site generator Hugo&rsquo;s excellent ship frequency, I previously spent at least a few minutes every couple weeks <a href="https://github.com/gohugoio/hugo/releases">downloading the new extended version</a>. With a Bash function, I only need to pass in the version number, and the upgrade happens in a few seconds.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># Hugo install or upgrade</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">function</span> gethugo <span style="color:#f92672">()</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>    wget -q -P tmp/ https://github.com/gohugoio/hugo/releases/download/v<span style="color:#e6db74">&#34;</span>$@<span style="color:#e6db74">&#34;</span>/hugo_extended_<span style="color:#e6db74">&#34;</span>$@<span style="color:#e6db74">&#34;</span>_Linux-64bit.tar.gz
</span></span><span style="display:flex;"><span>    tar xf tmp/hugo_extended_<span style="color:#e6db74">&#34;</span>$@<span style="color:#e6db74">&#34;</span>_Linux-64bit.tar.gz -C tmp/
</span></span><span style="display:flex;"><span>    sudo mv -f tmp/hugo /usr/local/bin/
</span></span><span style="display:flex;"><span>    rm -rf tmp/
</span></span><span style="display:flex;"><span>    hugo version
</span></span><span style="display:flex;"><span><span style="color:#f92672">}</span>
</span></span></code></pre></div><p>The <code>$@</code> notation simply takes all the arguments given, replacing its spot in the function. To run the above function and download Hugo version 0.57.2, you use the command <code>gethugo 0.57.2</code>.</p>
<h3 id="bash-function-for-downloading-a-specific-go-version">Bash function for downloading a specific Go version</h3>
<p>I&rsquo;ve got one for <a href="https://golang.org/">Golang</a>, too:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#66d9ef">function</span> getgolang <span style="color:#f92672">()</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>    sudo rm -rf /usr/local/go
</span></span><span style="display:flex;"><span>    wget -q -P tmp/ https://dl.google.com/go/go<span style="color:#e6db74">&#34;</span>$@<span style="color:#e6db74">&#34;</span>.linux-amd64.tar.gz
</span></span><span style="display:flex;"><span>    sudo tar -C /usr/local -xzf tmp/go<span style="color:#e6db74">&#34;</span>$@<span style="color:#e6db74">&#34;</span>.linux-amd64.tar.gz
</span></span><span style="display:flex;"><span>    rm -rf tmp/
</span></span><span style="display:flex;"><span>    go version
</span></span><span style="display:flex;"><span><span style="color:#f92672">}</span>
</span></span></code></pre></div><h3 id="bash-function-for-adding-a-gitlab-remote">Bash function for adding a GitLab remote</h3>
<p>Or how about a function that adds a remote origin URL for GitLab to the current repository?</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#66d9ef">function</span> glab <span style="color:#f92672">()</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>    git remote set-url origin --add git@gitlab.com:<span style="color:#e6db74">&#34;</span>$@<span style="color:#e6db74">&#34;</span>/<span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>PWD##*/<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>.git
</span></span><span style="display:flex;"><span>    git remote -v
</span></span><span style="display:flex;"><span><span style="color:#f92672">}</span>
</span></span></code></pre></div><p>With <code>glab username</code>, you can create a new <code>origin</code> URL for the current Git repository with our <code>username</code> on GitLab.com. Pushing to a new remote URL <a href="/posts/how-to-write-bash-one-liners-for-cloning-and-managing-github-and-gitlab-repositories/#a-bash-one-liner-to-create-and-push-many-repositories-on-gitlab">automatically creates a new private GitLab repository</a>, so this is a useful shortcut for creating backups!</p>
<p>Bash functions are really only limited by the possibilities of scripting, of which there are, practically, few limits. If there&rsquo;s anything you do on a frequent basis that requires typing a few lines into a terminal, you can probably create a Bash function for it!</p>
<h2 id="bash-prompt">Bash prompt</h2>
<p>Besides directory contents, it&rsquo;s also useful to see the full path of the directory we&rsquo;re in. The Bash prompt can show us this path, along with other useful information like our current Git branch. To make it more readable, you can define colours for each part of the prompt. Here&rsquo;s how you can set up our prompt in <code>.bashrc</code> to accomplish this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># Colour codes are cumbersome, so let&#39;s name them</span>
</span></span><span style="display:flex;"><span>txtcyn<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;\[\e[0;96m\]&#39;</span> <span style="color:#75715e"># Cyan</span>
</span></span><span style="display:flex;"><span>txtpur<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;\[\e[0;35m\]&#39;</span> <span style="color:#75715e"># Purple</span>
</span></span><span style="display:flex;"><span>txtwht<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;\[\e[0;37m\]&#39;</span> <span style="color:#75715e"># White</span>
</span></span><span style="display:flex;"><span>txtrst<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;\[\e[0m\]&#39;</span>    <span style="color:#75715e"># Text Reset</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Which (C)olour for what part of the prompt?</span>
</span></span><span style="display:flex;"><span>pathC<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>txtcyn<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>gitC<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>txtpur<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>pointerC<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>txtwht<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>normalC<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>txtrst<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Get the name of our branch and put parenthesis around it</span>
</span></span><span style="display:flex;"><span>gitBranch<span style="color:#f92672">()</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>    git branch 2&gt; /dev/null | sed -e <span style="color:#e6db74">&#39;/^[^*]/d&#39;</span> -e <span style="color:#e6db74">&#39;s/* \(.*\)/(\1)/&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Build the prompt</span>
</span></span><span style="display:flex;"><span>export PS1<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>pathC<span style="color:#e6db74">}</span><span style="color:#e6db74">\w </span><span style="color:#e6db74">${</span>gitC<span style="color:#e6db74">}</span><span style="color:#e6db74">\$(gitBranch) </span><span style="color:#e6db74">${</span>pointerC<span style="color:#e6db74">}</span><span style="color:#e6db74">\$</span><span style="color:#e6db74">${</span>normalC<span style="color:#e6db74">}</span><span style="color:#e6db74"> &#34;</span>
</span></span></code></pre></div><p>Result:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>~/github/myrepo <span style="color:#f92672">(</span>master<span style="color:#f92672">)</span> $
</span></span></code></pre></div><p>Naming the colours helps to easily identify where one colour starts and stops, and where the next one begins. The prompt that you see in our terminal is defined by the string following <code>export PS1</code>, with each component of the prompt set with an <a href="https://www.tldp.org/HOWTO/Bash-Prompt-HOWTO/bash-prompt-escape-sequences.html">escape sequence</a>. Let&rsquo;s break that down:</p>
<ul>
<li><code>\w</code> displays the current working directory,</li>
<li><code>\$(gitBranch)</code> calls the <code>gitBranch</code> function defined above, which displays the current Git branch,</li>
<li><code>\$</code> will display a &ldquo;$&rdquo; if you are a normal user or in normal user mode, and a &ldquo;#&rdquo; if you are root.</li>
</ul>
<p>The <a href="https://www.gnu.org/software/bash/manual/html_node/Controlling-the-Prompt.html">full list of Bash escape sequences</a> can help us display many more bits of information, including even the time and date! Bash prompts are highly customizable and individual, so feel free to set it up any way you please.</p>
<p>Here are a few options that put information front and centre and can help us to work more efficiently.</p>
<h3 id="for-the-procrastination-averse">For the procrastination-averse</h3>
<p>Username and current time with seconds, in 24-hour HH:MM:SS format:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>export PS1<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>userC<span style="color:#e6db74">}</span><span style="color:#e6db74">\u </span><span style="color:#e6db74">${</span>normalC<span style="color:#e6db74">}</span><span style="color:#e6db74">at \t &gt;&#34;</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>user at 09:35:55 &gt;
</span></span></code></pre></div><h3 id="for-those-who-always-like-to-know-where-they-stand">For those who always like to know where they stand</h3>
<p>Full file path on a separate line, and username:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>export PS1<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>pathC<span style="color:#e6db74">}</span><span style="color:#e6db74">\w</span><span style="color:#e6db74">${</span>normalC<span style="color:#e6db74">}</span><span style="color:#e6db74">\n\u:&#34;</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>~/github/myrepo
</span></span><span style="display:flex;"><span>user:
</span></span></code></pre></div><h3 id="for-the-minimalist">For the minimalist</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>export PS1<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;&gt;&#34;</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>&gt;
</span></span></code></pre></div><p>We can build many practical prompts with just the basic escape sequences; once you start to integrate functions with prompts, as in the Git branch example, things can get really complicated. Whether this amount of complication is an addition or a detriment to your productivity, only you can know for sure!</p>
<p>Many fancy Bash prompts are possible with programs readily available with a quick search. I&rsquo;ve intentionally not provided samples here because, well, if you can tend to get as excited about this stuff as I can, it might be a couple hours before you get back to what you were doing before you started reading this post, and I just can&rsquo;t have that on my conscience. 🥺</p>
<p>We&rsquo;ve hopefully struck a nice balance now between time invested and usefulness gained from our Bash configuration file! I hope you use your newly-recovered keystroke capacity for good.</p>
]]></content></entry><entry><title type="html">How to set up a fresh Ubuntu desktop using only dotfiles and bash scripts</title><link href="https://victoria.dev/archive/how-to-set-up-a-fresh-ubuntu-desktop-using-only-dotfiles-and-bash-scripts/"/><id>https://victoria.dev/archive/how-to-set-up-a-fresh-ubuntu-desktop-using-only-dotfiles-and-bash-scripts/</id><author><name>Victoria Drake</name></author><published>2019-08-19T07:58:18-04:00</published><updated>2019-08-19T07:58:18-04:00</updated><content type="html"><![CDATA[<p>One of my most favorite things about open source files on GitHub is the ability to see how others do (what some people might call) mundane things, like set up their <code>.bashrc</code> and other dotfiles. While I&rsquo;m not as enthusiastic about ricing as I was when I first came to the Linux side, I still get pretty excited when I find a config setting that makes things prettier and faster, and thus, better.</p>
<p>I recently came across a few such things, particularly in <a href="https://github.com/tomnomnom">Tom Hudson&rsquo;s</a> dotfiles. Tom seems to like to script things, and some of those things include automatically setting up symlinks, and installing Ubuntu repository applications and other programs. This got me thinking. Could I automate the set up of a new machine to replicate my current one?</p>
<p>Being someone generally inclined to take things apart in order to see how they work, I know I&rsquo;ve messed up my laptop on occasion. (Usually when I&rsquo;m away from home, and my backup hard drive isn&rsquo;t.) On those rare but really inconvenient situations when my computer becomes a shell of its former self, (ba-dum-ching) it&rsquo;d be quite nice to have a fast, simple way of putting Humpty Dumpty back together again, just the way I like.</p>
<p>In contrast to creating a <a href="https://askubuntu.com/questions/19901/how-to-make-a-disk-image-and-restore-from-it-later">disk image and restoring it later</a>, a collection of bash scripts is easier to create, maintain, and move around. They require no special utilities, only an external transportation method. It&rsquo;s like passing along the recipe, instead of the whole bundt cake. (Mmm, cake.)</p>
<p>Additionally, functionality like this would be super useful when setting up a virtual machine, or VM, or even just a virtual private server, or VPS. (Both of which, now that I write this, would probably make more forgiving targets for my more destructive experiments&hellip; live and learn!)</p>
<p>Well, after some grepping and Googling and digging around, I now have a suite of scripts that can do this:</p>
<video controls="controls" poster="cover.jpg">
    <source src="setup.mp4" type="video/mp4" />
</video>
<p>This is the tail end of a test run of the set up scripts on a fresh Ubuntu desktop, loaded off a bootable USB. It had all my programs and settings restored in under three minutes!</p>
<p>This post will cover how to achieve the automatic set up of a computer running Ubuntu Desktop using bash scripts. This exact process was last used on Ubuntu 19.10; see my <a href="https://github.com/victoriadrake/dotfiles">dotfiles master branch</a> for the latest configuration. The majority of the information covered is applicable to all the Linux desktop flavours, though some syntax may differ. The bash scripts cover three main areas: linking dotfiles, installing software from Ubuntu and elsewhere, and setting up the desktop environment. We&rsquo;ll cover each of these areas and go over the important bits so that you can begin to craft your own scripts.</p>
<h2 id="dotfiles">Dotfiles</h2>
<p>Dotfiles are what most Linux enthusiasts call configuration files. They typically live in the user&rsquo;s home directory (denoted in bash scripts with the <a href="https://www.tldp.org/LDP/abs/html/internal.html#BUILTINREF">builtin</a> variable <code>$HOME</code>) and control the appearance and behavior of all kinds of programs. The file names begin with <code>.</code>, which denotes hidden files in Linux (hence &ldquo;dot&rdquo; files). Here are some common dotfiles and ways in which they&rsquo;re useful.</p>
<h3 id="bashrc"><code>.bashrc</code></h3>
<p>The <code>.bashrc</code> file is a list of commands executed at startup by interactive, non-login shells. <a href="https://www.tldp.org/LDP/abs/html/intandnonint.html">Interactive vs non-interactive shells</a> can be a little confusing, but aren&rsquo;t necessary for us to worry about here. For our purposes, any time you open a new terminal, see a prompt, and can type commands into it, your <code>.bashrc</code> was executed.</p>
<p>Lines in this file can help improve your workflow by creating aliases that reduce keystrokes, or by displaying a helpful prompt with useful information. It can even run user-created programs, like <a href="https://github.com/victoriadrake/eddie-terminal">Eddie</a>. For more ideas, you can have a look at <a href="https://github.com/victoriadrake/dotfiles/blob/ubuntu-19.10/.bashrc">my <code>.bashrc</code> file on GitHub</a>.</p>
<h3 id="vimrc"><code>.vimrc</code></h3>
<p>The <code>.vimrc</code> dotfile configures the champion of all text editors, <a href="https://www.vim.org/about.php">Vim</a>. (If you haven&rsquo;t yet wielded the powers of the keyboard shortcuts, I highly recommend <a href="https://vim-adventures.com/">a fun game to learn Vim with</a>.)</p>
<p>In <code>.vimrc</code>, we can set editor preferences such as display settings, colours, and custom keyboard shortcuts. You can take a look at <a href="https://github.com/victoriadrake/dotfiles/blob/ubuntu-19.10/.vimrc">my <code>.vimrc</code> on GitHub</a>.</p>
<p>Other dotfiles may be useful depending on the programs you use, such as <code>.gitconfig</code> or <code>.tmux.conf</code>. Exploring dotfiles on GitHub is a great way to get a sense of what&rsquo;s available and useful to you!</p>
<h2 id="linking-dotfiles">Linking dotfiles</h2>
<p>We can use a script to create symbolic links, or <a href="https://en.wikipedia.org/wiki/Symbolic_link#POSIX_and_Unix-like_operating_systems">symlinks</a> for all our dotfiles. This allows us to keep all the files in a central repository, where they can easily be managed, while also providing a sort of placeholder in the spot that our programs expect the configuration file to be found. This is typically, but not always, the user home directory. For example, since I store my dotfiles on GitHub, I keep them in a directory with a path like <code>~/github/dotfiles/</code> while the files themselves are symlinked, resulting in a path like <code>~/.vimrc</code>.</p>
<p>To programmatically check for and handle any existing files and symlinks, then create new ones, we can use <a href="https://github.com/victoriadrake/dotfiles/blob/ubuntu-19.10/scripts/symlink.sh">this elegant shell script</a>. I compliment it only because I blatantly stole the core of it from <a href="https://github.com/tomnomnom/dotfiles/blob/master/setup.sh">Tom&rsquo;s setup script</a>, so I can&rsquo;t take the credit for how lovely it is.</p>
<p>The <code>symlink.sh</code> script works by attempting to create symlinks for each dotfile in our <code>$HOME</code>. It first checks to see if a symlink already exists, or if a regular file or directory with the same name exists. In the former case, the symlink is removed and remade; in the latter, the file or directory is renamed, then the symlink is made.</p>
<h2 id="installing-software">Installing software</h2>
<p>One of the beautiful things about exploring shell scripts is discovering how much can be achieved using only the command line. As someone whose first exposure to computers was through a graphical operating system, I find working in the terminal to be refreshingly fast.</p>
<p>With Ubuntu, most programs we likely require are available through the default Ubuntu software repositories. We typically search for these with the command <code>apt search &lt;program&gt;</code> and install them with <code>sudo apt install &lt;program&gt;</code>. Some software we&rsquo;d like may not be in the default repositories, or may not be offered there in the most current version. In these cases, we can still install these programs in Ubuntu using a <a href="https://en.wikipedia.org/wiki/Ubuntu#Package_Archives">PPA, or Personal Package Archive</a>. We&rsquo;ll just have to be careful that the PPAs we choose are from the official sources.</p>
<p>If a program we&rsquo;d like doesn&rsquo;t appear in the default repositories or doesn&rsquo;t seem to have a PPA, we may still be able to install it via command line. A quick search for &ldquo;<program> installation command line&rdquo; should get some answers.</p>
<p>Since bash scripts are just a collection of commands that we could run individually in the terminal, creating a script to install all our desired programs is as straightforward as putting all the commands into a script file. I chose to organize my installation scripts between the default repositories, which are installed by <a href="https://github.com/victoriadrake/dotfiles/blob/ubuntu-19.10/scripts/aptinstall.sh">my <code>aptinstall.sh</code> script</a>, and programs that involve external sources, handled with <a href="https://github.com/victoriadrake/dotfiles/blob/ubuntu-19.10/scripts/programs.sh">my <code>programs.sh</code> script</a>.</p>
<h2 id="setting-up-the-desktop-environment">Setting up the desktop environment</h2>
<p>On the recent occasions when I&rsquo;ve gotten a fresh desktop (intentionally or otherwise) I always seem to forget how long it takes to remember, find, and then change all the desktop environment settings. Keyboard shortcuts, workspaces, sound settings, night mode&hellip; it adds up!</p>
<p>Thankfully, all these settings have to be stored somewhere in a non-graphical format, which means that if we can discover how that&rsquo;s done, we can likely find a way to easily manipulate the settings with a bash script. Lo and behold the terminal command, <code>gsettings list-recursively</code>.</p>
<p>There are a heck of a lot of settings for GNOME desktop environment. We can make the list easier to scroll through (if, like me, you&rsquo;re sometimes the type of person to say &ldquo;Just let me look at everything and figure out what I want!&rdquo;) by piping to <code>less</code>: <code>gsettings list-recursively | less</code>. Alternatively, if we have an inkling as to what we might be looking for, we can use <code>grep</code>: <code>gsettings list-recursively | grep 'keyboard'</code>.</p>
<p>We can manipulate our settings with the <code>gsettings set</code> command. It can sometimes be difficult to find the syntax for the setting we want, so when we&rsquo;re first building our script, I recommend using the GUI to make the changes, then finding the <code>gsettings</code> line we changed and recording its value.</p>
<p>For some inspiration, you can view <a href="https://github.com/victoriadrake/dotfiles/blob/ubuntu-19.10/scripts/desktop.sh">my <code>desktop.sh</code> settings script on GitHub</a>.</p>
<h2 id="putting-it-all-together">Putting it all together</h2>
<p>Having modular scripts (one for symlinks, two for installing programs, another for desktop settings) is useful for both keeping things organized and for being able to run some but not all of the automated set up. For instance, if I were to set up a VPS in which I only use the command line, I wouldn&rsquo;t need to bother with installing graphical programs or desktop settings.</p>
<p>In cases where I do want to run all the scripts, however, doing so one-by-one is a little tedious. Thankfully, since bash scripts can themselves be run by terminal commands, we can simply write another master script to run them all!</p>
<p>Here&rsquo;s my master script to handle the set up of a new Ubuntu desktop machine:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span>./symlink.sh
</span></span><span style="display:flex;"><span>./aptinstall.sh
</span></span><span style="display:flex;"><span>./programs.sh
</span></span><span style="display:flex;"><span>./desktop.sh
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">## Get all upgrades</span>
</span></span><span style="display:flex;"><span>sudo apt upgrade -y
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">## See our bash changes</span>
</span></span><span style="display:flex;"><span>source ~/.bashrc
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">## Fun hello</span>
</span></span><span style="display:flex;"><span>figlet <span style="color:#e6db74">&#34;... and we&#39;re back!&#34;</span> | lolcat
</span></span></code></pre></div><p>I threw in the upgrade line for good measure. It will make sure that the programs installed on our fresh desktop have the latest updates. Now a simple, single bash command will take care of everything!</p>
<p>You may have noticed that, while our desktop now looks and runs familiarly, these scripts don&rsquo;t cover one very important area: our files. Hopefully, you have a back up method for those that involves some form of reliable external hardware. If not, and if you tend to put your work in external repository hosts like GitHub or GitLab, I do have a way to <a href="/posts/how-to-write-bash-one-liners-for-cloning-and-managing-github-and-gitlab-repositories/">automatically clone and back up your GitHub repositories with bash one-liners</a>.</p>
<p>Relying on external repository hosts doesn&rsquo;t offer 100% coverage, however. Files that you wouldn&rsquo;t put in an externally hosted repository (private or otherwise) consequently can&rsquo;t be pulled. Git ignored objects that can&rsquo;t be generated from included files, like private keys and secrets, will not be recreated. Those files, however, are likely small enough that you could fit a whole bunch on a couple encrypted USB flash drives (and if you don&rsquo;t have private key backups, maybe you ought to do that first?).</p>
<p>That said, I hope this post has given you at least some inspiration as to how dotfiles and bash scripts can help to automate setting up a fresh desktop. If you come up with some settings you find useful, please help others discover them by sharing your dotfiles, too!</p>
]]></content></entry><entry><title type="html">How to write Bash one-liners for cloning and managing GitHub and GitLab repositories</title><link href="https://victoria.dev/archive/how-to-write-bash-one-liners-for-cloning-and-managing-github-and-gitlab-repositories/"/><id>https://victoria.dev/archive/how-to-write-bash-one-liners-for-cloning-and-managing-github-and-gitlab-repositories/</id><author><name>Victoria Drake</name></author><published>2019-08-06T10:55:19-04:00</published><updated>2019-08-06T10:55:19-04:00</updated><content type="html"><![CDATA[<p>Few things are more satisfying to me than one elegant line of Bash that automates hours of tedious work. As part of some recent explorations into <a href="/blog/how-to-set-up-a-fresh-ubuntu-desktop-using-only-dotfiles-and-bash-scripts/">automatically re-creating my laptop with Bash scripts</a>, I wanted to find a way to easily clone my GitHub-hosted repositories to a new machine. After a bit of digging around, I wrote a one-liner that did just that. Then, in the spirit of not putting all our eggs in the same basket, I wrote another one-liner to automatically create and push to GitLab-hosted backups as well. Here they are.</p>
<h2 id="a-bash-one-liner-to-clone-all-your-github-repositories">A Bash one-liner to clone all your GitHub repositories</h2>
<p>Caveat: you&rsquo;ll need a list of the GitHub repositories you want to clone. The good thing about that is it gives you full agency to choose just the repositories you want on your machine, instead of going in whole-hog.</p>
<p>You can easily clone GitHub repositories without entering your password each time by using HTTPS with your <a href="https://docs.github.com/en/get-started/getting-started-with-git/caching-your-github-credentials-in-git">15-minute cached credentials</a> or, my preferred method, by <a href="https://docs.github.com/en/authentication/connecting-to-github-with-ssh">connecting to GitHub with SSH</a>. For brevity I&rsquo;ll assume we&rsquo;re going with the latter, and our SSH keys are set up.</p>
<p>Given a list of GitHub URLs in the file <code>gh-repos.txt</code>, like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>git@github.com:username/first-repository.git
</span></span><span style="display:flex;"><span>git@github.com:username/second-repository.git
</span></span><span style="display:flex;"><span>git@github.com:username/third-repository.git
</span></span></code></pre></div><p>We run:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>xargs -n1 git clone &lt; gh-repos.txt
</span></span></code></pre></div><p>This clones all the repositories on the list into the current folder. This same one-liner works for GitLab repositories as well, if you substitute the appropriate URLs.</p>
<h3 id="whats-going-on-here">What&rsquo;s going on here</h3>
<p>There are two halves to this one-liner: the input, counterintuitively on the right side, and the part that makes stuff happen, on the left. We could make the order of these parts more intuitive (maybe?) by writing the same command like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>&lt;gh-repos.txt xargs -n1 git clone
</span></span></code></pre></div><p>To run a command for each line of our input, <code>gh-repos.txt</code>, we use <code>xargs -n1</code>. The tool <code>xargs</code> reads items from input and executes any commands it finds (it will <code>echo</code> if it doesn&rsquo;t find any). By default, it assumes that items are separated by spaces; new lines also works and makes our list easier to read. The flag <code>-n1</code> tells <code>xargs</code> to use <code>1</code> argument, or in our case, one line, per command. We build our command with <code>git clone</code>, which <code>xargs</code> then executes for each line. Ta-da.</p>
<h2 id="a-bash-one-liner-to-create-and-push-many-repositories-on-gitlab">A Bash one-liner to create and push many repositories on GitLab</h2>
<p>GitLab, unlike GitHub, lets us do this nifty thing where we don&rsquo;t have to use the website to make a new repository first. We can <a href="https://docs.gitlab.com/ee/user/project/working_with_projects.html#create-a-new-project-with-git-push">create a new GitLab repository from our terminal</a>. The newly created repository defaults to being set as Private, so if we want to make it Public on GitLab, we&rsquo;ll have to do that manually later.</p>
<p>The GitLab docs tell us to push to create a new project using <code>git push --set-upstream</code>, but I don&rsquo;t find this to be very convenient for using GitLab as a backup. As I work with my repositories in the future, I&rsquo;d like to run one command that pushes to both GitHub <em>and</em> GitLab without additional effort on my part.</p>
<p>To make this Bash one-liner work, we&rsquo;ll also need a list of repository URLs for GitLab (ones that don&rsquo;t exist yet). We can easily do this by copying our GitHub repository list, opening it up with Vim, and doing a <a href="https://vim.fandom.com/wiki/Search_and_replace">search-and-replace</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>cp gh-repos.txt gl-repos.txt
</span></span><span style="display:flex;"><span>vim gl-repos.txt
</span></span><span style="display:flex;"><span>:%s/<span style="color:#ae81ff">\&lt;</span>github<span style="color:#ae81ff">\&gt;</span>/gitlab/g
</span></span><span style="display:flex;"><span>:wq
</span></span></code></pre></div><p>This produces <code>gl-repos.txt</code>, which looks like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>git@gitlab.com:username/first-repository.git
</span></span><span style="display:flex;"><span>git@gitlab.com:username/second-repository.git
</span></span><span style="display:flex;"><span>git@gitlab.com:username/third-repository.git
</span></span></code></pre></div><p>We can create these repositories on GitLab, add the URLs as remotes, and push our code to the new repositories by running:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>awk -F<span style="color:#e6db74">&#39;\/|(\.git)&#39;</span> <span style="color:#e6db74">&#39;{system(&#34;cd ~/FULL/PATH/&#34; $2 &#34; &amp;&amp; git remote set-url origin --add &#34; $0 &#34; &amp;&amp; git push&#34;)}&#39;</span> gl-repos.txt
</span></span></code></pre></div><p>Hang tight and I&rsquo;ll explain it; for now, take note that <code>~/FULL/PATH/</code> should be the full path to the directory containing our GitHub repositories.</p>
<p>We do have to make note of a couple assumptions:</p>
<ol>
<li>The name of the directory on your local machine that contains the repository is the same as the name of the repository in the URL (this will be the case if it was cloned with the one-liner above);</li>
<li>Each repository is currently checked out to the branch you want pushed, ie. <code>master</code>.</li>
</ol>
<p>The one-liner could be expanded to handle these assumptions, but it is the humble opinion of the author that at that point, we really ought to be writing a Bash script.</p>
<h3 id="whats-going-on-here-1">What&rsquo;s going on here</h3>
<p>Our Bash one-liner uses each line (or URL) in the <code>gl-repos.txt</code> file as input. With <code>awk</code>, it splits off the name of the directory containing the repository on our local machine, and uses these pieces of information to build our larger command. If we were to <code>print</code> the output of <code>awk</code>, we&rsquo;d see:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>cd ~/FULL/PATH/first-repository <span style="color:#f92672">&amp;&amp;</span> git remote set-url origin --add git@gitlab.com:username/first-repository.git <span style="color:#f92672">&amp;&amp;</span> git push
</span></span><span style="display:flex;"><span>cd ~/FULL/PATH/second-repository <span style="color:#f92672">&amp;&amp;</span> git remote set-url origin --add git@gitlab.com:username/second-repository.git <span style="color:#f92672">&amp;&amp;</span> git push
</span></span><span style="display:flex;"><span>cd ~/FULL/PATH/third-repository <span style="color:#f92672">&amp;&amp;</span> git remote set-url origin --add git@gitlab.com:username/third-repository.git <span style="color:#f92672">&amp;&amp;</span> git push
</span></span></code></pre></div><p>Let&rsquo;s look at how we build this command.</p>
<h4 id="splitting-strings-with-awk">Splitting strings with <code>awk</code></h4>
<p>The tool <code>awk</code> can split input based on <a href="https://www.gnu.org/software/gawk/manual/html_node/Command-Line-Field-Separator.html">field separators</a>. The default separator is a whitespace character, but we can change this by passing the <code>-F</code> flag. Besides single characters, we can also use a <a href="https://www.gnu.org/software/gawk/manual/html_node/Regexp-Field-Splitting.html#Regexp-Field-Splitting">regular expression field separator</a>. Since our repository URLs have a set format, we can grab the repository names by asking for the substring between the slash character <code>/</code> and the end of the URL, <code>.git</code>.</p>
<p>One way to accomplish this is with our regex <code>\/|(\.git)</code>:</p>
<ul>
<li><code>\/</code> is an escaped <code>/</code> character;</li>
<li><code>|</code> means &ldquo;or&rdquo;, telling awk to match either expression;</li>
<li><code>(\.git)</code> is the capture group at the end of our URL that matches &ldquo;.git&rdquo;, with an escaped <code>.</code> character. This is a bit of a cheat, as &ldquo;.git&rdquo; isn&rsquo;t strictly splitting anything (there&rsquo;s nothing on the other side) but it&rsquo;s an easy way for us to take this bit off.</li>
</ul>
<p>Once we&rsquo;ve told <code>awk</code> where to split, we can grab the right substring with the <a href="https://www.gnu.org/software/gawk/manual/html_node/Fields.html#index-_0024-_0028dollar-sign_0029_002c-_0024-field-operator">field operator</a>. We refer to our fields with a <code>$</code> character, then by the field&rsquo;s column number. In our example, we want the second field, <code>$2</code>. Here&rsquo;s what all the substrings look like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>1: git@gitlab.com:username
</span></span><span style="display:flex;"><span>2: first-repository
</span></span></code></pre></div><p>To use the whole string, or in our case, the whole URL, we use the field operator <code>$0</code>. To write the command, we just substitute the field operators for the repository name and URL. Running this with <code>print</code> as we&rsquo;re building it can help to make sure we&rsquo;ve got all the spaces right.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>awk -F<span style="color:#e6db74">&#39;\/|(\.git)&#39;</span> <span style="color:#e6db74">&#39;{print &#34;cd ~/FULL/PATH/&#34; $2 &#34; &amp;&amp; git remote set-url origin --add &#34; $0 &#34; &amp;&amp; git push&#34;}&#39;</span> gl-repos.txt
</span></span></code></pre></div><h4 id="running-the-command">Running the command</h4>
<p>We build our command inside the parenthesis of <code>system()</code>. By using this as the output of <code>awk</code>, each command will run as soon as it is built and output. The <code>system()</code> function creates a <a href="https://en.wikipedia.org/wiki/Child_process">child process</a> that executes our command, then returns once the command is completed. In plain English, this lets us perform the Git commands on each repository, one-by-one, without breaking from our main process in which <code>awk</code> is doing things with our input file. Here&rsquo;s our final command again, all put together.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>awk -F<span style="color:#e6db74">&#39;\/|(\.git)&#39;</span> <span style="color:#e6db74">&#39;{system(&#34;cd ~/FULL/PATH/&#34; $2 &#34; &amp;&amp; git remote set-url origin --add &#34; $0 &#34; &amp;&amp; git push&#34;)}&#39;</span> gl-repos.txt
</span></span></code></pre></div><h4 id="using-our-backups">Using our backups</h4>
<p>By adding the GitLab URLs as remotes, we&rsquo;ve simplified the process of pushing to both externally hosted repositories. If we run <code>git remote -v</code> in one of our repository directories, we&rsquo;ll see:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>origin  git@github.com:username/first-repository.git <span style="color:#f92672">(</span>fetch<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>origin  git@github.com:username/first-repository.git <span style="color:#f92672">(</span>push<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>origin  git@gitlab.com:username/first-repository.git <span style="color:#f92672">(</span>push<span style="color:#f92672">)</span>
</span></span></code></pre></div><p>Now, simply running <code>git push</code> without arguments will push the current branch to both remote repositories.</p>
<p>We should also note that <code>git pull</code> will generally only try to pull from the remote repository you originally cloned from (the URL marked <code>(fetch)</code> in our example above). Pulling from multiple Git repositories at the same time is possible, but complicated, and beyond the scope of this post. Here&rsquo;s an <a href="https://astrofloyd.wordpress.com/2015/05/05/git-pushing-to-and-pulling-from-multiple-remote-locations-remote-url-and-pushurl/">explanation of pushing and pulling to multiple remotes</a> to help get you started, if you&rsquo;re curious. The <a href="https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes">Git documentation on remotes</a> may also be helpful.</p>
<h2 id="to-elaborate-on-the-succinctness-of-bash-one-liners">To elaborate on the succinctness of Bash one-liners</h2>
<p>Bash one-liners, when understood, can be fun and handy shortcuts. At the very least, being aware of tools like <code>xargs</code> and <code>awk</code> can help to automate and alleviate a lot of tediousness in our work. However, there are some downsides.</p>
<p>In terms of an easy-to-understand, maintainable, and approachable tool, Bash one-liners suck. They&rsquo;re usually more complicated to write than a Bash script using <code>if</code> or <code>while</code> loops, and certainly more complicated to read. It&rsquo;s likely that when we write them, we&rsquo;ll miss a single quote or closing parenthesis somewhere; and as I hope this post demonstrates, they can take quite a bit of explaining, too. So why use them?</p>
<p>Imagine reading a recipe for baking a cake, step by step. You understand the methods and ingredients, and gather your supplies. Then, as you think about it, you begin to realize that if you just throw all the ingredients at the oven in precisely the right order, a cake will instantly materialize. You try it, and it works!</p>
<p>That would be pretty satisfying, wouldn&rsquo;t it?</p>
]]></content></entry><entry><title type="html">A quick guide to changing your GitHub username</title><link href="https://victoria.dev/archive/a-quick-guide-to-changing-your-github-username/"/><id>https://victoria.dev/archive/a-quick-guide-to-changing-your-github-username/</id><author><name>Victoria Drake</name></author><published>2019-07-28T15:19:13-04:00</published><updated>2019-07-28T15:19:13-04:00</updated><content type="html"><![CDATA[<p>This being the 2,38947234th and probably last time I&rsquo;ll change my username, (marriage is permanent, right?) I thought I&rsquo;d better write a quick post on how this transition can be achieved as smoothly as possible. You can read <a href="https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-github-user-account/managing-user-account-settings/changing-your-github-username">official instructions on how to change your GitHub username</a> here, and they will tell you how to do it and what happens. The following is a quick guide to some things to consider <em>afterwards.</em></p>
<h2 id="where-to-make-changes">Where to make changes</h2>
<ol>
<li>Change username in <a href="https://github.com/settings/admin">GitHub account settings.</a></li>
<li>If using GitHub Pages, change name of your &ldquo;username.github.io&rdquo; repository.</li>
<li>If using other services that point to your &ldquo;username.github.io&rdquo; repository address, update them.</li>
<li>If using Netlify, you <em>may</em> want to sign in and reconnect your repositories. (Mine still worked, but due to a possibly unrelated issue, I&rsquo;m not positive.)</li>
<li>Sign in to Travis CI and other integrations (find them in your repository Settings tab -&gt; Integrations &amp; services). This will update your username there.</li>
<li>Update your local files and repository links with <em>very carefully executed</em> <code>find</code> and <code>sed</code> commands, and push back changes to GitHub.</li>
<li>Redeploy any websites you may have with your updated GitHub link.</li>
<li>Fix any links around the web to your profile, your repositories, or Gists you may have shared.</li>
</ol>
<h2 id="local-file-updates">Local file updates</h2>
<p>Here are some suggestions for strings to search and replace your username in.</p>
<ul>
<li><code>github.com/username</code> (References to your GitHub page in READMEs or in website copy)</li>
<li><code>username.github.io</code> (Links to your GitHub Page)</li>
<li><code>git@github.com:username</code> (Git config remote ssh urls)</li>
<li><code>travis-ci.com/username</code> (Travis badges in READMEs)</li>
<li><code>shields.io/github/.../username</code> (Shields badges in READMEs, types include <code>contributors</code>, <code>stars</code>, <code>tags</code>, and more)</li>
</ul>
<p>You can quickly identify where the above strings are located using this command for each string:</p>
<p><code>grep -rnw -e 'foobar'</code></p>
<p>This will recursively (<code>r</code>) search all files for strings matching the whole (<code>w</code>) pattern (<code>e</code>) provided and prefix results with the line numbers (<code>n</code>) so you can easily find them.</p>
<p>Using <code>find</code> and <code>sed</code> can make these changes much faster. See <a href="/posts/how-to-replace-a-string-in-a-dozen-old-blog-posts-with-one-sed-terminal-command/">this article on search and replace</a>.</p>
<p>Enjoy your new handle! (I hope it sticks.)</p>
]]></content></entry><entry><title type="html">Two ways to deploy a public GitHub Pages site from a private Hugo repository</title><link href="https://victoria.dev/archive/two-ways-to-deploy-a-public-github-pages-site-from-a-private-hugo-repository/"/><id>https://victoria.dev/archive/two-ways-to-deploy-a-public-github-pages-site-from-a-private-hugo-repository/</id><author><name>Victoria Drake</name></author><published>2019-04-22T10:05:15-04:00</published><updated>2019-04-22T10:05:15-04:00</updated><content type="html"><![CDATA[<p>Tools like Travis CI and Netlify offer some pretty nifty features, like seamlessly deploying your GitHub Pages site when changes are pushed to its repository. Along with a static site generator like Hugo, keeping a blog up to date is pretty painless.</p>
<p>I&rsquo;ve used Hugo to build my site for years, but until this past week I&rsquo;d never hooked up my Pages repository to any deployment service. Why? Because using a tool that built my site before deploying it seemed to require having the whole recipe in one place - and if you&rsquo;re using GitHub Pages with the free version of GitHub, <a href="https://docs.github.com/en/pages/getting-started-with-github-pages/changing-the-visibility-of-your-github-pages-site">that place is public</a>. That means that all my three-in-the-morning bright ideas and messy unfinished (and unfunny) drafts would be publicly available - and no amount of continuous convenience was going to convince me to do that.</p>
<p>So I kept things separated, with Hugo&rsquo;s messy behind-the-scenes stuff in a local Git repository, and the generated <code>public/</code> folder pushing to my GitHub Pages remote repository. Each time I wanted to deploy my site, I&rsquo;d have to get on my laptop and <code>hugo</code> to build my site, then <code>cd public/ &amp;&amp; git add . &amp;&amp; git commit</code>&hellip; etc etc. And all was well, except for the nagging feeling that there was a better way to do this.</p>
<p>I wrote another article a little while back about <a href="/blog/a-remote-sync-solution-for-ios-and-linux-git-and-working-copy/">using GitHub and Working Copy</a> to make changes to my repositories on my iPad whenever I&rsquo;m out and about. It seemed off to me that I could do everything except deploy my site from my iPad, so I set out to change that.</p>
<p>A couple three-in-the-morning bright ideas and a revoked access token later (oops), I now have not one but <em>two</em> ways to deploy to my public GitHub Pages repository from an entirely separated, private GitHub repository. In this post, I&rsquo;ll take you through achieving this with <a href="https://travis-ci.com/">Travis CI</a> or using <a href="http://netlify.com/">Netlify</a> and <a href="https://www.gnu.org/software/make/">Make</a>.</p>
<p>There&rsquo;s nothing hackish about it - my public GitHub Pages repository still looks the same as it does when I pushed to it locally from my terminal. Only now, I&rsquo;m able to take advantage of a couple great deployment tools to have the site update whenever I push to my private repo, whether I&rsquo;m on my laptop or out and about with my iPad.</p>
<figure><img src="/archive/two-ways-to-deploy-a-public-github-pages-site-from-a-private-hugo-repository/im-on-a-bridge.jpg"
    alt="Hashtag: you did not push from there"><figcaption>
      <p>#YouDidNotPushFromThere</p>
    </figcaption>
</figure>

<p>This article assumes you have working knowledge of Git and GitHub Pages. If not, you may like to spin off some browser tabs from my articles on <a href="/blog/a-remote-sync-solution-for-ios-and-linux-git-and-working-copy/">using GitHub and Working Copy</a> and <a href="/blog/how-i-ditched-wordpress-and-set-up-my-custom-domain-https-site-for-almost-free/">building a site with Hugo and GitHub Pages</a> first.</p>
<p>Let&rsquo;s do it!</p>
<h2 id="private-to-public-github-pages-deployment-with-travis-ci">Private-to-public GitHub Pages deployment with Travis CI</h2>
<p>Travis CI has the built-in ability (♪) to <a href="https://docs.travis-ci.com/user/deployment/pages/">deploy to GitHub Pages</a> following a successful build. They do a decent job in the docs of explaining how to add this feature, especially if you&rsquo;ve used Travis CI before&hellip; which I haven&rsquo;t. Don&rsquo;t worry, I did the bulk of the figuring-things-out for you.</p>
<ul>
<li>Travis CI gets all its instructions from a configuration file in the root of your repository called <code>.travis.yml</code></li>
<li>You need to provide a <a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token">GitHub personal access token</a> as a secure encrypted variable, which you can generate using <code>travis</code> on the command line</li>
<li>Once your script successfully finishes doing what you&rsquo;ve told it to do (not necessarily what you <em>want</em> it to do but that&rsquo;s a whole other blog post), Travis will deploy your build directory to a repository you can specify with the <code>repo</code> configuration variable.</li>
</ul>
<h3 id="setting-up-the-travis-configuration-file">Setting up the Travis configuration file</h3>
<p>Create a new configuration file for Travis with the filename <code>.travis.yml</code> (note the leading &ldquo;.&rdquo;). These scripts are very customizable and I struggled to find a relevant example to use as a starting point - luckily, you don&rsquo;t have that problem!</p>
<p>Here&rsquo;s my basic <code>.travis.yml</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yml" data-lang="yml"><span style="display:flex;"><span><span style="color:#f92672">git</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">depth</span>: <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">env</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">global</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#ae81ff">HUGO_VERSION=&#34;0.54.0&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">matrix</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#ae81ff">YOUR_ENCRYPTED_VARIABLE</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">install</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">wget -q https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_${HUGO_VERSION}_Linux-64bit.tar.gz</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">tar xf hugo_${HUGO_VERSION}_Linux-64bit.tar.gz</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">mv hugo ~/bin/</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">script</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">hugo --gc --minify</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">deploy</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">provider</span>: <span style="color:#ae81ff">pages</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">skip-cleanup</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">github-token</span>: <span style="color:#ae81ff">$GITHUB_TOKEN</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">keep-history</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">local-dir</span>: <span style="color:#ae81ff">public</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">repo</span>: <span style="color:#ae81ff">gh-username/gh-username.github.io</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">target-branch</span>: <span style="color:#ae81ff">master</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">verbose</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">on</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">branch</span>: <span style="color:#ae81ff">master</span>
</span></span></code></pre></div><p>This script downloads and installs Hugo, builds the site with the garbage collection and minify <a href="https://gohugo.io/commands/hugo/#synopsis">flags</a>, then deploys the <code>public/</code> directory to the specified <code>repo</code> - in this example, your public GitHub Pages repository. You can read about each of the <code>deploy</code> configuration options <a href="https://docs.travis-ci.com/user/deployment/pages/#further-configuration">here</a>.</p>
<p>To <a href="https://docs.travis-ci.com/user/environment-variables#defining-encrypted-variables-in-travisyml">add the GitHub personal access token as an encrypted variable</a>, you don&rsquo;t need to manually edit your <code>.travis.yml</code>. The <code>travis</code> gem commands below will encrypt and add the variable for you when you run them in your repository directory.</p>
<p>First, install <code>travis</code> with <code>sudo gem install travis</code>.</p>
<p>Then <a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token">generate your GitHub personal access token</a>, copy it (it only shows up once!) and run the commands below in your repository root, substituting your token for the kisses:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>travis login --pro --github-token xxxxxxxxxxxxxxxxxxxxxxxxxxx
</span></span><span style="display:flex;"><span>travis encrypt GITHUB_TOKEN<span style="color:#f92672">=</span>xxxxxxxxxxxxxxxxxxxxxxxxxxx --add env.matrix
</span></span></code></pre></div><p>Your encrypted token magically appears in the file. Once you&rsquo;ve committed <code>.travis.yml</code> to your private Hugo repository, Travis CI will run the script and if the build succeeds, will deploy your site to your public GitHub Pages repo. Magic!</p>
<p>Travis will always run a build each time you push to your private repository. If you don&rsquo;t want to trigger this behavior with a particular commit, <a href="https://docs.travis-ci.com/user/customizing-the-build/#skipping-a-build">add the <code>skip</code> command to your commit message</a>.</p>
<p><em>Yo that&rsquo;s cool but I like Netlify.</em></p>
<p>Okay fine.</p>
<h2 id="deploying-to-a-separate-repository-with-netlify-and-make">Deploying to a separate repository with Netlify and Make</h2>
<p>We can get Netlify to do our bidding by using a Makefile, which we&rsquo;ll run with Netlify&rsquo;s build command.</p>
<p>Here&rsquo;s what our <code>Makefile</code> looks like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-makefile" data-lang="makefile"><span style="display:flex;"><span>SHELL<span style="color:#f92672">:=</span>/bin/bash
</span></span><span style="display:flex;"><span>BASEDIR<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>CURDIR<span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>OUTPUTDIR<span style="color:#f92672">=</span>public
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">.PHONY</span><span style="color:#f92672">:</span> all
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">all</span><span style="color:#f92672">:</span> clean get_repository build deploy
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">.PHONY</span><span style="color:#f92672">:</span> clean
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">clean</span><span style="color:#f92672">:</span>
</span></span><span style="display:flex;"><span> @echo <span style="color:#e6db74">&#34;Removing public directory&#34;</span>
</span></span><span style="display:flex;"><span> rm -rf <span style="color:#66d9ef">$(</span>BASEDIR<span style="color:#66d9ef">)</span>/<span style="color:#66d9ef">$(</span>OUTPUTDIR<span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">.PHONY</span><span style="color:#f92672">:</span> get_repository
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">get_repository</span><span style="color:#f92672">:</span>
</span></span><span style="display:flex;"><span> @echo <span style="color:#e6db74">&#34;Getting public repository&#34;</span>
</span></span><span style="display:flex;"><span> git clone https://github.com/gh-username/gh-username.github.io.git public
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">.PHONY</span><span style="color:#f92672">:</span> build
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">build</span><span style="color:#f92672">:</span>
</span></span><span style="display:flex;"><span> @echo <span style="color:#e6db74">&#34;Generating site&#34;</span>
</span></span><span style="display:flex;"><span> hugo --gc --minify
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">.PHONY</span><span style="color:#f92672">:</span> deploy
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">deploy</span><span style="color:#f92672">:</span>
</span></span><span style="display:flex;"><span> @echo <span style="color:#e6db74">&#34;Preparing commit&#34;</span>
</span></span><span style="display:flex;"><span> @cd <span style="color:#66d9ef">$(</span>OUTPUTDIR<span style="color:#66d9ef">)</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span> <span style="color:#f92672">&amp;&amp;</span> git config user.email <span style="color:#e6db74">&#34;you@youremail.com&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span> <span style="color:#f92672">&amp;&amp;</span> git config user.name <span style="color:#e6db74">&#34;Your Name&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span> <span style="color:#f92672">&amp;&amp;</span> git add . <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span> <span style="color:#f92672">&amp;&amp;</span> git status <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span> <span style="color:#f92672">&amp;&amp;</span> git commit -m <span style="color:#e6db74">&#34;Deploy via Makefile&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span> <span style="color:#f92672">&amp;&amp;</span> git push -f -q https://<span style="color:#66d9ef">$(</span>GITHUB_TOKEN<span style="color:#66d9ef">)</span>@github.com/gh-username/gh-username.github.io.git master
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> @echo <span style="color:#e6db74">&#34;Pushed to remote&#34;</span>
</span></span></code></pre></div><p>To preserve the Git history of our separate GitHub Pages repository, we&rsquo;ll first clone it, build our new Hugo site to it, and then push it back to the Pages repository. This script first removes any existing <code>public/</code> folder that might contain files or a Git history. It then clones our Pages repository to <code>public/</code>, builds our Hugo site (essentially updating the files in <code>public/</code>), then takes care of committing the new site to the Pages repository.</p>
<p>In the <code>deploy</code> section, you&rsquo;ll notice lines starting with <code>&amp;&amp;</code>. These are chained commands. Since Make <a href="https://www.gnu.org/software/make/manual/html_node/Execution.html#Execution">invokes a new sub-shell for each line</a>, it starts over with every new line from our root directory. To get our <code>cd</code> to stick and avoid running our Git commands in the project root directory, we&rsquo;re chaining the commands and using the backslash character to <a href="http://clarkgrubb.com/makefile-style-guide#breaking-long-lines">break long lines</a> for readability.</p>
<p>By chaining our commands, we&rsquo;re able to <a href="https://stackoverflow.com/questions/6116548/how-to-tell-git-to-use-the-correct-identity-name-and-email-for-a-given-project">configure our Git identity</a>, add all our updated files, and create a commit for our Pages repository.</p>
<p>Similarly to using Travis CI, we&rsquo;ll need to pass in a <a href="https://github.com/settings/tokens">GitHub personal access token</a> to push to our public GitHub Pages repository - only Netlify doesn&rsquo;t provide a straightforward way to encrypt the token in our Makefile.</p>
<p>Instead, we&rsquo;ll use Netlify&rsquo;s <a href="https://www.netlify.com/docs/continuous-deployment/#build-environment-variables">Build Environment Variables</a>, which live safely in our site settings in the Netlify app. We can then call our token variable in the Makefile. We use it to push (quietly, to avoid printing the token in logs) to our Pages repository by <a href="https://stackoverflow.com/questions/44773415/how-to-push-a-commit-to-github-from-a-circleci-build-using-a-personal-access-tok">passing it in the remote URL</a>.</p>
<p>To avoid printing the token in Netlify&rsquo;s logs, we suppress <a href="https://www.gnu.org/software/make/manual/html_node/Echoing.html#Echoing">recipe echoing</a> for that line with the leading <code>@</code> character.</p>
<p>With your Makefile in the root of your private GitHub repository, you can set up Netlify to run it for you.</p>
<h3 id="setting-up-netlify">Setting up Netlify</h3>
<p>Getting set up with Netlify via the <a href="https://app.netlify.com/">web UI</a> is straightforward. Once you sign in with GitHub, choose the private GitHub repository where your Hugo site lives. The next page Netlify takes you to lets you enter deploy settings:</p>
<p><img src="netlify-new-site.png" alt="Create a new site page"></p>
<p>You can specify the build command that will run your Makefile (<code>make all</code> for this example). The branch to deploy and the publish directory don&rsquo;t matter too much in our specific case, since we&rsquo;re only concerned with pushing to a separate repository. You can enter the typical <code>master</code> deploy branch and <code>public</code> publish directory.</p>
<p>Under &ldquo;Advanced build settings&rdquo; click &ldquo;New variable&rdquo; to add your GitHub personal access token as a Build Environment Variable. In our example, the variable name is <code>GITHUB_TOKEN</code>. Click &ldquo;Deploy site&rdquo; to make the magic happen.</p>
<p>If you&rsquo;ve already previously set up your repository with Netlify, find the settings for Continuous Deployment under Settings &gt; Build &amp; deploy.</p>
<p>Netlify will build your site each time you push to the private repository. If you don&rsquo;t want a particular commit to trigger a build, <a href="https://www.netlify.com/docs/continuous-deployment/#skipping-a-deploy">add <code>[skip ci]</code> in your Git commit message</a>.</p>
<h3 id="same-same-but-different">Same same but different</h3>
<p>One effect of using Netlify this way is that your site will be built in two places: one is the separate, public GitHub Pages repository that the Makefile pushes to, and the other is your Netlify site that deploys on their CDN from your linked private GitHub repository. The latter is useful if you&rsquo;re going to play with <a href="https://www.netlify.com/blog/2016/07/20/introducing-deploy-previews-in-netlify/">Deploy Previews</a> and other Netlify features, but those are outside the scope of this post.</p>
<p>The main point is that your GitHub Pages site is now updated in your public repo. Yay!</p>
<h2 id="go-forth-and-deploy-fearlessly">Go forth and deploy fearlessly</h2>
<p>I hope the effect of this new information is that you feel more able to update your sites, wherever you happen to be. The possibilities are endless - at home on your couch with your laptop, out cafe-hopping with your iPad, or in the middle of a first date on your phone. Endless!</p>
<figure><img src="/archive/two-ways-to-deploy-a-public-github-pages-site-from-a-private-hugo-repository/date-deploy.png"
    alt="Don&#39;t update your site from your phone on a date"><figcaption>
      <p>Don&rsquo;t do stuff on your phone when you&rsquo;re on a date. Not if you want a second one, anyway.</p>
    </figcaption>
</figure>

]]></content></entry><entry><title type="html">A remote sync solution for iOS and Linux: Git and Working Copy</title><link href="https://victoria.dev/archive/a-remote-sync-solution-for-ios-and-linux-git-and-working-copy/"/><id>https://victoria.dev/archive/a-remote-sync-solution-for-ios-and-linux-git-and-working-copy/</id><author><name>Victoria Drake</name></author><published>2019-03-15T11:55:28-04:00</published><updated>2020-12-02T11:55:28-04:00</updated><content type="html"><![CDATA[<p>I&rsquo;m always looking for pockets of time in which I can be productive. If you add up the minutes you spend in limbo while waiting in line, commuting, or waiting for food delivery (just me?), you may just find an extra hour or two in your day.</p>
<p>To take full advantage of these bits of time, I needed a solution that let me pick up work on my Git repositories wherever I happen to be. That means a remote sync solution that bridges my iOS devices (iPad and iPhone) and my Linux machine.</p>
<p>After a lot of trial and error, I&rsquo;ve found one that works really well. With synced Git repositories on iOS, I can seamlessly pick up work for any of my repositories on the go.</p>
<h2 id="components">Components</h2>
<ul>
<li><a href="https://workingcopy.app">Working Copy app</a> ($15.99 one-time pro-unlock and well worth it)</li>
<li><a href="https://ia.net/writer">iA Writer app</a> ($8.99 one-time purchase for iOS, also available on Mac, Windows, and Android)</li>
<li>GitHub repositories</li>
</ul>
<h2 id="get-set-up">Get set up</h2>
<p>Here are the steps to setting up that I&rsquo;ll walk you through in this article.</p>
<ol>
<li>Create your remote repository</li>
<li>Clone repository to iPad with Working Copy</li>
<li>Open and edit files with iA Writer</li>
<li>Push changes back to remote</li>
<li>Pull changes from repository on your computer</li>
</ol>
<p>This system is straightforward to set up whether you&rsquo;re a command line whiz or just getting into Git. Let&rsquo;s do it!</p>
<h3 id="create-your-remote-repository">Create your remote repository</h3>
<p>Create a public or private repository on GitHub.</p>
<p>If you&rsquo;re creating a new repository, you can follow GitHub&rsquo;s instructions to push some files to it from your computer, or you can add files later from your iOS device.</p>
<h3 id="clone-repository-to-ios-with-working-copy">Clone repository to iOS with Working Copy</h3>
<p>Download <a href="https://workingcopy.app">Working Copy</a> from the App Store. It&rsquo;s a fantastic app. Developer <a href="https://twitter.com/palmin">Anders Borum</a> has a steady track record of frequent updates and incorporating the latest features for iOS apps, like <a href="https://workingcopy.app/manual/dragdrop">drag and drop</a> on iPad. I think he&rsquo;s fairly priced his product in light of the work he puts into maintaining and enhancing it.</p>
<p>In Working Copy, find the gear icon in the top left corner and touch to open Settings.</p>
<p>Tap on SSH Keys, and you&rsquo;ll see this screen:</p>
<p>SSH keys, or Secure Shell keys, are access credentials used in the <a href="https://en.wikipedia.org/wiki/Secure_Shell">SSH protocol</a>. Your key is a password that your device will use to securely connect with your remote repository host - GitHub, in this example. Since anyone with your SSH keys can potentially pretend to be you and gain access to your files, it&rsquo;s important not to share them accidentally, like in a screenshot on a blog post.</p>
<p>Tap on the second line that looks like <strong>WorkingCopy@iPad-xxxxxxxx</strong> to get this screen:</p>
<p>Working Copy supports easy connection to GitHub. Tap <strong>Connect With GitHub</strong> to bring up some familiar sign-in screens that will authorize Working Copy to access your account(s).</p>
<p>Once connected, tap the <strong>+</strong> symbol in the top right of the side bar to add a new repository. Choose <strong>Clone repository</strong> to bring up this screen:</p>
<p>Here, you can either manually input the remote URL, or simply choose from the list of repositories that Working Copy fetches from your connected account. When you make your choice, the app clones the repository to your device and it will show up in the sidebar. You&rsquo;re connected!</p>
<h3 id="open-and-edit-files-with-ia-writer">Open and edit files with iA Writer</h3>
<p>One of the (many) reasons I adore <a href="https://ia.net/writer">iA Writer</a> is its ability to select your freshly cloned remote repository as a Library Location. To enable this, first open your Files app. On the Browse screen, tap the overflow menu (three dots) in the top right and choose <strong>Edit</strong>.</p>
<p>Turn on Working Copy as a location option:</p>
<p>Then in the iA Writer app:</p>
<ol>
<li>From the main Library list, in the top right of the sidebar, tap <strong>Edit</strong>.</li>
<li>Tap <strong>Add Location&hellip;</strong>.</li>
<li>A helpful popup appears. Tap <strong>OK</strong>.</li>
<li>From the Working Copy location, tap <strong>Select</strong> in the top right, then choose the repository folder.</li>
<li>Tap <strong>Open</strong>, then <strong>Done</strong>.</li>
</ol>
<p>Your remote repository now appears as a Location in the sidebar. Tap on it to work within this directory.</p>
<p>While inside this location, new files you create (by tapping the pencil-and-paper icon in the top right corner) will be saved to this folder locally. As you work, iA Writer automatically saves your progress. Next, we&rsquo;ll look at pushing those files and changes back to your remote.</p>
<h3 id="push-changes-back-to-remote">Push changes back to remote</h3>
<p>Once you&rsquo;ve made changes to your files, open Working Copy again. You should see a yellow dot on your changed repository.</p>
<p>Tap on your repository name, then on <strong>Repository Status and Configuration</strong> at the top of the sidebar. Your changed files will be indicated by yellow dots or green <strong>+</strong> symbols. These mean that you&rsquo;ve modified or added files, respectively.</p>
<p>Working Copy is a sweet iOS Git client, and you can tap on your files to see additional information including a comparison of changes (&ldquo;diff&rdquo;) as well as status and Git history. You can even edit files right within the app, with <a href="https://workingcopyapp.com/manual/edit">syntax highlighting</a> for its many supported languages. For now, we&rsquo;ll look at how to push your changed work to your remote repository.</p>
<p>On the <strong>Repository Status and Configuration</strong> page, you&rsquo;ll see right at the top that there are changes to be committed. If you&rsquo;re new to Git, this is like &ldquo;saving your changes&rdquo; to your Git history, something typically done with the terminal command <a href="https://git-scm.com/docs/git-commit"><code>git commit</code></a>. You can think of this as saving the files that we&rsquo;ll want to send to the GitHub repository. Tap <strong>Commit changes</strong>.</p>
<p>Enter your commit message, and select the files you want to add. Toggle the <strong>Push</strong> switch to send everything to your remote repository when you commit the files. Then tap <strong>Commit</strong>.</p>
<p>You&rsquo;ll see a progress bar as your files are uploaded, and then a confirmation message on the status screen.</p>
<p>Congratulations! Your changes are now present in your remote repository on GitHub. You&rsquo;ve successfully synced your files remotely!</p>
<h3 id="pull-changes-from-repository-on-your-computer">Pull changes from repository on your computer</h3>
<p>To bring your updated files full circle to your computer, you pull them from the GitHub repository. I prefer to use the terminal for this as it&rsquo;s quick and easy, but GitHub also offers a <a href="https://docs.github.com/en/desktop/overview/getting-started-with-github-desktop?platform=windows">graphical client</a> if terminal commands seem a little alien for now.</p>
<p>If you started with the GitHub repository, you can clone it to a folder on your computer by following <a href="https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository">these instructions</a>.</p>
<h3 id="staying-in-sync">Staying in sync</h3>
<p>When you update your work on your computer, you&rsquo;ll use Git to push your changes to the remote repository. To do this, you can use GitHub&rsquo;s <a href="https://docs.github.com/en/desktop/overview/getting-started-with-github-desktop?platform=windows">graphical client</a>, or follow <a href="https://docs.github.com/en/get-started/importing-your-projects-to-github/importing-source-code-to-github/adding-an-existing-project-to-github-using-the-command-line">these instructions</a>.</p>
<p>On your iOS device, Working Copy makes pulling and pushing as simple as a single tap. On the Repository Status and Configuration page, tap on the remote name under <strong>Remotes</strong>.</p>
<p>Then tap <strong>Synchronize</strong>. Working Copy will take care of the details of pushing your committed changes and/or pulling any new changes it finds from the remote repository.</p>
<h2 id="work-anywhere">Work anywhere</h2>
<p>For a Git-based developer and work-anywhere-aholic like me, this set up couldn&rsquo;t be more convenient. Working Copy really makes staying in sync with my remote repositories seamless, nevermind the ability to work with any of my GitHub repos on the go.</p>
<p>I most recently used this set up to get some writing done while hanging out in the atrium of Washington DC&rsquo;s National Portrait Gallery, which is pleasantly photogenic.</p>
<p>Happy working! If you enjoyed this post, there&rsquo;s a lot more where this came from! I write about computing, cybersecurity, and leading great technical teams. You can subscribe below to see new posts first.</p>
<a href="https://medium.com/@victoriadotdev/subscribe" target="_blank" rel="noopener noreferrer" class="subscribe-button">
    <span class="subscribe-icon">📧</span>
    <span class="subscribe-text">Subscribe</span>
</a>

]]></content></entry><entry><title type="html">On Doing Great Things</title><link href="https://victoria.dev/posts/on-doing-great-things/"/><id>https://victoria.dev/posts/on-doing-great-things/</id><author><name>Victoria Drake</name></author><published>2019-03-08T18:36:15-05:00</published><updated>2019-03-08T18:36:15-05:00</updated><content type="html"><![CDATA[<p>It&rsquo;s International Women&rsquo;s Day, and I&rsquo;m thinking about Grace Hopper.</p>
<p><a href="https://en.m.wikipedia.org/wiki/Grace_Hopper">Grace Hopper</a> was an amazing lady who did great things. She envisioned and helped create programming languages that translate English terms into machine code. She persevered in her intention to join the US Navy from the time she was rejected at 34 years old, to being sworn in to the US Navy Reserve three years later, to retiring with the rank of commander at age 60&hellip; then was recalled (twice) and promoted to the rank of captain at the age of 67. She advocated for distributed networks and developed computer testing standards we use today, among other achievements too numerous to list here.</p>
<p>By my read, throughout her life, she kept her focus on her work. She did great things because she could do them, and felt some duty to do them. Her work speaks for itself.</p>
<p>I recently came across a sizeable rock denoting a rather small, quiet park. It looks like this:</p>
<p><img src="grace-murray-hopper-park.jpeg#center" alt="Signage on a rock denoting Grace Murray Hopper Park"></p>
<p>When I first saw this park, I thought it in no way did this great lady justice. But upon some reflection, its lack of assumption and grandeur grew on me. And today, it drew to the forefront something that&rsquo;s been on my mind.</p>
<p>I try and contribute regularly to the wide world of technology, usually through building things, writing, and mentorship. I sometimes get asked to participate in female-focused tech events. I hear things like, &ldquo;too few developers are women,&rdquo; or &ldquo;we need more women in blockchain,&rdquo; or &ldquo;we need more female coders.&rdquo;</p>
<p>For some time I haven&rsquo;t been sure how to respond, because while my answer isn&rsquo;t &ldquo;yes,&rdquo; it&rsquo;s not exactly &ldquo;no,&rdquo; either. It&rsquo;s really, &ldquo;no, because&hellip;&rdquo; and it&rsquo;s because I&rsquo;m afraid. I&rsquo;m afraid of misrepresenting myself, my values, and my goals.</p>
<p>Discrimination and racism are real things. They exist in the minds and attitudes of a very small percentage of very loud people, as they always will. These people aren&rsquo;t, however, the majority. They are small.</p>
<p>I think that on the infrequent occasions when we encounter these people, we should do our best to lead by example. We should have open minds, tell our stories, listen to theirs. Try and learn something. That&rsquo;s all.</p>
<p>When I present myself, I don&rsquo;t point out that I&rsquo;m a woman. I don&rsquo;t align myself with &ldquo;women in tech&rdquo; or seek to represent them. I don&rsquo;t go to women-only meetings or support organizations that discriminate against men, or anyone at all. It&rsquo;s not because I&rsquo;m insecure as a woman, or ashamed that I&rsquo;m a woman, or some other inflammatory adjective that lately shows up in conjunction with being female. It&rsquo;s because I&rsquo;ve no reason to point out my gender, any more than needing to point out that my hair is black, or that I&rsquo;m short. It&rsquo;s obvious and simultaneously irrelevant.</p>
<p>When I identify with a group, I talk about the go-getters who wake up at 0500 every day and go work out—no matter the weather, or whether they feel like it. I tell stories about the people I&rsquo;ve met in different countries around the world, who left home, struck out on their own, and had an adventure, because they saw value in the experience. I identify with people who constantly build things, try things, design and make things, and then share those things with the world, because they love to do so. This is how I see myself. This is what matters to me.</p>
<p>Like the unassuming park named after an amazing woman, when truly great things are done, they are done relatively quietly. Not done for the fanfare of announcing them to the world, but for the love of the thing itself. So go do great things, please. The world still needs them.</p>
]]></content></entry><entry><title type="html">Building Code Quality Culture Through Commit Standards</title><link href="https://victoria.dev/posts/building-code-quality-culture-through-commit-standards/"/><id>https://victoria.dev/posts/building-code-quality-culture-through-commit-standards/</id><author><name>Victoria Drake</name></author><published>2018-08-06T08:54:56-04:00</published><updated>2018-08-06T08:54:56-04:00</updated><content type="html"><![CDATA[<p>When I first started leading engineering teams, I thought high quality code was about efficient algorithms and architecture. I was wrong. The biggest indicator of a team&rsquo;s engineering maturity shows up in their commit history.</p>
<p>A clean commit history reveals a team that thinks about maintainability, communicates context effectively, and takes pride in their craft. Messy commits signal the opposite: rushed work, poor communication habits, and a culture that prioritizes shipping over sustainability (guaranteed to make the <a href="/posts/the-descent-is-harder-than-the-climb/">descent harder than the climb</a>). As an engineering leader, establishing commit standards builds the foundation for everything else you want to achieve.</p>
<h2 id="the-cost-of-poor-commit-culture">The Cost of Poor Commit Culture</h2>
<p>I’ve seen this common pattern especially often in the post-startup phase. Issues that should have been a 30-minute investigation stretch into hours due to useless commit messages like &ldquo;fix stuff,&rdquo; &ldquo;updates,&rdquo; or &ldquo;refactor”—messages that make it impossible to understand the intent behind each change. One of the least thoughtful comments I’ve ever heard on the subject went along the lines of, “Capable engineers should just be able to read the code and understand the change, we don’t need good commit messages.” Right. Have fun explaining to the director that a straightforward bug fix requires days of reading lines of code because the team allows lazy commits.</p>
<p>It’s arguable that the real cost isn’t even the lost revenue—it’s the erosion of trust. Teams with lazy commits start questioning each other&rsquo;s work quality, code reviews became adversarial, and velocity plummets as a result.</p>
<p>Useful commit standards create a culture with:</p>
<ul>
<li><strong>Context preservation</strong> - Future team members (including your future self) can understand not just what changed, but why</li>
<li><strong>Accountability</strong> - Engineers take ownership of their changes and think through the impact</li>
<li><strong>Knowledge transfer</strong> - Institutional knowledge doesn&rsquo;t walk out the door when someone leaves</li>
<li><strong>Debugging efficiency</strong> - When things break, you can quickly trace the source and reasoning</li>
</ul>
<p>Poor commit habits compound over time. What starts as a small productivity tax becomes a massive technical debt that slows down everything your team tries to accomplish.</p>
<h2 id="making-standards-stick-the-template-approach">Making Standards Stick: The Template Approach</h2>
<p>The biggest challenge with commit standards isn&rsquo;t defining them—it&rsquo;s getting your team to actually follow them consistently. I&rsquo;ve seen too many teams create detailed commit guidelines that gather dust in a README file while engineers continue writing &ldquo;fixed stuff&rdquo; messages.</p>
<p>The solution is to make good practices easier than bad ones. Instead of expecting people to remember complex guidelines under deadline pressure, embed the standards directly into the workflow.</p>
<p>Here&rsquo;s the team commit template I&rsquo;ve successfully rolled out across multiple organizations:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>## If applied, this commit will...
</span></span><span style="display:flex;"><span>## [Add/Fix/Remove/Update/Refactor/Document] [issue #id] [summary]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>## Why is it necessary? (Bug fix, feature, improvements?)
</span></span><span style="display:flex;"><span>-
</span></span><span style="display:flex;"><span>## How does the change address the issue?
</span></span><span style="display:flex;"><span>-
</span></span><span style="display:flex;"><span>## What side effects does this change have?
</span></span><span style="display:flex;"><span>-
</span></span></code></pre></div><p>To implement this across your team, add it to your onboarding process:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>git config --global commit.template ~/.gitmessage
</span></span></code></pre></div><p>The template serves multiple purposes beyond just formatting. It forces engineers to think through the &ldquo;why&rdquo; behind their changes, which often reveals edge cases or better approaches before the code ever reaches review. I&rsquo;ve watched junior engineers discover design flaws simply by trying to articulate their commit message.</p>
<p>More importantly, it creates consistency without feeling like micromanagement. Engineers appreciate having a framework with a short feedback loop rather than being told their commit messages are &ldquo;wrong&rdquo; after the fact.</p>
<h3 id="connecting-work-to-business-impact">Connecting Work to Business Impact</h3>
<p>One pattern I&rsquo;ve noticed across high-performing teams is how they connect individual commits to larger business objectives. Beyond linking to issue numbers, this creates traceability from business requirements to implementation details.</p>
<p>When commits reference issues consistently, several things happen:</p>
<ul>
<li><strong>Product managers can track feature progress</strong> without constantly asking for updates</li>
<li><strong>Support teams can quickly identify which changes might relate to customer issues</strong></li>
<li><strong>Security audits become straightforward</strong> when you need to trace the history of sensitive code</li>
<li><strong>Technical debt discussions become data-driven</strong> when you can quantify how much time is spent on maintenance vs. features</li>
</ul>
<p>Teams can use this traceability to make compelling cases for technical investments. When every bug fix commit links back to customer-reported issues, the cost of poor code quality becomes visible to leadership in a way that resonates.</p>
<h3 id="removing-friction-through-tooling">Removing Friction Through Tooling</h3>
<p>The most effective way to improve team habits is to make the desired behavior the easiest behavior. Beyond templates, consider how your development environment can reinforce good practices.</p>
<p>For teams using VS Code, I recommend setting up workspace configurations that include spell check and line wrapping for commit messages. This prevents the common problem of commit messages that are impossible to read in terminal displays.</p>
<p>More importantly, consider integrating commit quality into your CI/CD pipeline. Tools like <code>commitlint</code> can automatically validate commit message format, while pre-commit hooks can catch obvious issues before they reach the remote repository.</p>
<p>The goal is to provide immediate feedback when standards aren&rsquo;t met, rather than discovering problems during code review when fixing them is more disruptive to workflow.</p>
<h2 id="teaching-atomic-commits-through-code-review">Teaching Atomic Commits Through Code Review</h2>
<p>One of the most valuable lessons I learned as an engineering manager is that teaching atomic commits—one logical change per commit—dramatically improves code review quality and team collaboration.</p>
<p>When engineers make atomic commits, several things happen naturally:</p>
<ul>
<li><strong>Code reviews become faster</strong> because each commit tells a clear story</li>
<li><strong>Debugging becomes surgical</strong> because you can isolate exactly which logical change introduced a problem</li>
<li><strong>Feature rollbacks become safe</strong> when you can revert a specific piece of functionality without touching unrelated code</li>
<li><strong>Knowledge transfer improves</strong> because the commit history becomes a tutorial of how the system evolved</li>
</ul>
<p>The challenge is that atomic commits require more upfront thinking. Engineers need to plan their approach before writing code, which feels slower initially but pays massive dividends in team velocity over time.</p>
<p>I&rsquo;ve found the most effective way to teach this is through code review feedback that focuses on commit structure, not just code quality. When I see a pull request with one massive commit containing three different features, I ask the engineer to break it down and explain the reasoning for each piece.</p>
<h3 id="setting-team-expectations-for-commit-cleanup">Setting Team Expectations for Commit Cleanup</h3>
<p>Here&rsquo;s where leadership philosophy matters more than technical mechanics. Some teams insist on pristine linear history, while others prefer to preserve the full context of how work actually happened, including false starts and iterations.</p>
<p>I&rsquo;ve found the most success with a middle path: require clean, atomic commits for the main branch, but allow messy work-in-progress commits on feature branches. This gives engineers the freedom to commit frequently while working (which improves backup and collaboration) while ensuring the permanent history tells a clear story.</p>
<p>The key is establishing this expectation early and consistently. During code reviews, I focus on commit structure as much as code quality. A well-structured commit history often indicates clear thinking about the problem space.</p>
<p>For teams new to this practice, I recommend starting with simple squash merges:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>git reset --soft origin/master
</span></span><span style="display:flex;"><span>git commit
</span></span></code></pre></div><p>This approach takes multiple messy commits and combines them into one clean commit before merging to main. It&rsquo;s forgiving for engineers still learning atomic commit habits while maintaining clean project history.</p>
<h3 id="building-confidence-through-practice">Building Confidence Through Practice</h3>
<p>One concern I often hear from engineering managers is that requiring clean commits will slow down their team. In my experience, the opposite is true—but only after an initial learning period where engineers build confidence with git operations.</p>
<p>The most effective approach I&rsquo;ve found is pairing experienced engineers with those still learning git hygiene. When someone sees a colleague quickly reorganize commits using interactive rebase, it demystifies the process and builds confidence.</p>
<p>For selective commit cleanup, I teach this pattern:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>git reset --soft HEAD~5
</span></span><span style="display:flex;"><span>git commit -m <span style="color:#e6db74">&#34;New message for the combined commit&#34;</span>
</span></span></code></pre></div><p>This approach lets engineers combine the last few commits while preserving earlier work that was already well-structured. It&rsquo;s particularly useful for cleaning up the &ldquo;fix typo&rdquo; and &ldquo;address code review feedback&rdquo; commits that naturally accumulate during development.</p>
<p>The key is making this feel like a normal part of the development process, not a burdensome extra step. I&rsquo;ve found success by incorporating commit cleanup time into sprint planning and explicitly discussing it during retrospectives.</p>
<h3 id="when-to-invest-in-advanced-git-skills">When to Invest in Advanced Git Skills</h3>
<p>Interactive rebase is where engineering teams often get stuck. It&rsquo;s powerful enough to completely reorganize commit history, but complex enough that many engineers avoid it entirely. As a leader, you need to decide whether this level of git sophistication is worth the investment for your team.</p>
<p>I&rsquo;ve found that teams working on critical infrastructure or open source projects benefit significantly from advanced git skills. The ability to craft a well-structured commit history pays dividends when you&rsquo;re debugging production issues or when external contributors need to understand your codebase.</p>
<p>For most product teams, however, I recommend focusing on simpler patterns that achieve 80% of the benefit with 20% of the complexity. Interactive rebase can be intimidating, and I&rsquo;d rather have consistent, good-enough commits than inconsistent attempts at perfection.</p>
<p>That said, having at least one team member comfortable with complex git operations is valuable. They become the &ldquo;git expert&rdquo; who can help others when commits get tangled, and they can teach advanced techniques during pair programming sessions.</p>
<p>The key is matching your git standards to your team&rsquo;s maturity and project needs. A startup moving fast might prioritize different things than a team maintaining financial systems.</p>
<h2 id="encouraging-experimentation-through-safety-nets">Encouraging Experimentation Through Safety Nets</h2>
<p>One of the biggest barriers to adopting better git practices is fear of making mistakes. Engineers worry that attempting to clean up their commits will result in lost work or broken history. Git stash becomes invaluable as both a technical tool and a confidence builder.</p>
<p>I encourage teams to use <code>git stash</code> liberally when learning new git techniques. It creates a safety net that makes experimentation feel safe:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>git stash  <span style="color:#75715e"># Save current work</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Try some git operations</span>
</span></span><span style="display:flex;"><span>git stash pop  <span style="color:#75715e"># Restore work if needed</span>
</span></span></code></pre></div><p>This pattern is particularly useful when teaching engineers to clean up commits before submitting pull requests. They can stash their current work, experiment with interactive rebase or commit squashing, and easily recover if something goes wrong.</p>
<p>Beyond the technical benefits, stash encourages a more exploratory mindset around git. Engineers who feel comfortable experimenting with different approaches often develop better intuition for structuring their commits in the first place.</p>
<p>I&rsquo;ve also found that teams with good stash habits tend to have fewer &ldquo;work in progress&rdquo; commits cluttering their history. When engineers know they can easily save and restore work, they&rsquo;re more likely to commit only when they&rsquo;ve reached a logical checkpoint.</p>
<h2 id="creating-accountability-through-release-markers">Creating Accountability Through Release Markers</h2>
<p>Tags serve a purpose beyond marking releases—they create natural checkpoints for reflecting on code quality and team practices. When teams establish a regular tagging cadence, it forces conversations about what constitutes a release-worthy state.</p>
<p>I&rsquo;ve found that teams with good tagging habits naturally develop better commit discipline. Knowing that commits will be part of a tagged release creates a sense of permanence that encourages more thoughtful commit messages and cleaner history.</p>
<p>The process of creating a release tag often reveals quality issues that might otherwise slip through:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>git tag -a v1.2.0 -m <span style="color:#e6db74">&#34;Release: Enhanced user authentication&#34;</span>
</span></span><span style="display:flex;"><span>git push --follow-tags
</span></span></code></pre></div><p>When someone has to write a release message that summarizes the changes since the last tag, poorly structured commits become obvious. This creates a feedback loop that naturally improves commit quality over time.</p>
<p>Tags also enable powerful debugging workflows. When production issues arise, being able to quickly identify which release introduced a problem can dramatically reduce time to resolution. This capability becomes especially valuable as teams scale and the commit volume increases.</p>
<p>More importantly, tags create opportunities for celebration. Teams that regularly tag releases can look back at their progress and feel genuine accomplishment. This positive reinforcement helps sustain good commit habits even when deadlines pressure teams to cut corners.</p>
<h2 id="building-lasting-culture-change">Building Lasting Culture Change</h2>
<p>Establishing commit standards is ultimately about building a culture that values craftsmanship and communication. The technical practices matter, but the underlying mindset matters more.</p>
<p>The most successful transformations I&rsquo;ve led started with making the case for why commit quality matters to the team&rsquo;s goals. When engineers understand that better commits lead to faster debugging, easier code reviews, and more effective knowledge transfer, they become invested in improvement rather than resistant to new rules.</p>
<p>Implementation should be gradual and supportive rather than punitive. Start with commit message templates and basic guidelines. Celebrate improvements publicly during retrospectives. Use code review as a teaching opportunity rather than a barrier mechanism.</p>
<p>Most importantly, lead by example. When team members see you taking time to craft thoughtful commit messages and clean up your own commit history, it signals that these practices are genuinely valued rather than just bureaucratic overhead.</p>
<p>The payoff extends far beyond git hygiene. Teams that develop discipline around commit quality often improve in other areas too: code review thoroughness, documentation habits, and general attention to craft. These practices compound over time to create engineering cultures that can scale effectively and maintain high quality even under pressure.</p>
<p>Building this kind of culture takes patience and consistency, but the investment pays dividends in team velocity, code quality, and job satisfaction for years to come.</p>
<a href="https://medium.com/@victoriadotdev/subscribe" target="_blank" rel="noopener noreferrer" class="subscribe-button">
    <span class="subscribe-icon">📧</span>
    <span class="subscribe-text">Subscribe</span>
</a>

]]></content></entry><entry><title type="html">An automatic interactive pre-commit checklist, in the style of infomercials</title><link href="https://victoria.dev/archive/an-automatic-interactive-pre-commit-checklist-in-the-style-of-infomercials/"/><id>https://victoria.dev/archive/an-automatic-interactive-pre-commit-checklist-in-the-style-of-infomercials/</id><author><name>Victoria Drake</name></author><published>2018-07-23T09:38:09-04:00</published><updated>2018-07-23T09:38:09-04:00</updated><content type="html"><![CDATA[<p>What&rsquo;s that, you say? You&rsquo;ve become tired of regular old boring <em>paper checklists?</em> Well, my friend, today is your lucky day! You, yes, <em>you,</em> can become the proud owner of a brand-spanking-new <em>automatic interactive pre-commit hook checklist!</em> You&rsquo;re gonna love this! Your life will be so much easier! Just wait until your friends see you.</p>
<h2 id="whats-a-pre-commit-hook">What&rsquo;s a pre-commit hook</h2>
<p>Did you know that nearly <em>1 out of 5 coders</em> are too embarrassed to ask this question? Don&rsquo;t worry, it&rsquo;s perfectly normal. In the next 60 seconds we&rsquo;ll tell you all you need to know to pre-commit with confidence.</p>
<p>A <a href="https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks">Git hook</a> is a feature of Git that triggers custom scripts at useful moments. They can be used for all kinds of reasons to help you automate your work, and best of all, you already have them! In every repository that you initialize with <code>git init</code>, you&rsquo;ll have a set of example scripts living in <code>.git/hooks</code>. They all end with <code>.sample</code> and activating them is as easy as renaming the file to remove the <code>.sample</code> part.</p>
<p>Git hooks are not copied when a repository is cloned, so you can make them as personal as you like.</p>
<p>The useful moment in particular that we&rsquo;ll talk about today is the <em>pre-commit</em>. This hook is run after you do <code>git commit</code>, and before you write a commit message. Exiting this hook with a non-zero status will abort the commit, which makes it extremely useful for last-minute quality checks. Or, a bit of fun. Why not both!</p>
<h2 id="how-do-i-get-a-pre-commit-checklist">How do I get a pre-commit checklist</h2>
<p>I only want the best for my family and my commits, and that&rsquo;s why I choose an interactive pre-commit checklist. Not only is it fun to use, it helps to keep my projects safe from unexpected off-spec mistakes!</p>
<p>It&rsquo;s so easy! I just write a bash script that can read user input, and plop it into <code>.git/hooks</code> as a file named <code>pre-commit</code>. Then I do <code>chmod +x .git/hooks/pre-commit</code> to make it executable, and I&rsquo;m done!</p>
<p>Oh look, here comes an example bash script now!</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/sh
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;Would you like to play a game?&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Read user input, assign stdin to keyboard</span>
</span></span><span style="display:flex;"><span>exec &lt; /dev/tty
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">while</span> read -p <span style="color:#e6db74">&#34;Have you double checked that only relevant files were added? (Y/n) &#34;</span> yn; <span style="color:#66d9ef">do</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> $yn in
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">[</span>Yy<span style="color:#f92672">]</span> <span style="color:#f92672">)</span> break;;
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">[</span>Nn<span style="color:#f92672">]</span> <span style="color:#f92672">)</span> echo <span style="color:#e6db74">&#34;Please ensure the right files were added!&#34;</span>; exit 1;;
</span></span><span style="display:flex;"><span>        * <span style="color:#f92672">)</span> echo <span style="color:#e6db74">&#34;Please answer y (yes) or n (no):&#34;</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#66d9ef">continue</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">esac</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">done</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">while</span> read -p <span style="color:#e6db74">&#34;Has the documentation been updated? (Y/n) &#34;</span> yn; <span style="color:#66d9ef">do</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> $yn in
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">[</span>Yy<span style="color:#f92672">]</span> <span style="color:#f92672">)</span> break;;
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">[</span>Nn<span style="color:#f92672">]</span> <span style="color:#f92672">)</span> echo <span style="color:#e6db74">&#34;Please add or update the docs!&#34;</span>; exit 1;;
</span></span><span style="display:flex;"><span>        * <span style="color:#f92672">)</span> echo <span style="color:#e6db74">&#34;Please answer y (yes) or n (no):&#34;</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#66d9ef">continue</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">esac</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">done</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">while</span> read -p <span style="color:#e6db74">&#34;Do you know which issue or PR numbers to reference? (Y/n) &#34;</span> yn; <span style="color:#66d9ef">do</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> $yn in
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">[</span>Yy<span style="color:#f92672">]</span> <span style="color:#f92672">)</span> break;;
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">[</span>Nn<span style="color:#f92672">]</span> <span style="color:#f92672">)</span> echo <span style="color:#e6db74">&#34;Better go check those tracking numbers!&#34;</span>; exit 1;;
</span></span><span style="display:flex;"><span>        * <span style="color:#f92672">)</span> echo <span style="color:#e6db74">&#34;Please answer y (yes) or n (no):&#34;</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#66d9ef">continue</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">esac</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">done</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>exec &lt;&amp;-
</span></span></code></pre></div><h2 id="take-my-money">Take my money</h2>
<p>Don&rsquo;t delay! Take advantage <em>right now</em> of this generous <em>one-time offer!</em> An interactive pre-commit hook checklist can be yours, today, for the low, low price of&hellip; free? Wait, who wrote this script?</p>
]]></content></entry><entry><title type="html">Building High-Performance Engineering Teams Through Feedback Loops</title><link href="https://victoria.dev/posts/building-high-performance-engineering-teams-through-feedback-loops/"/><id>https://victoria.dev/posts/building-high-performance-engineering-teams-through-feedback-loops/</id><author><name>Victoria Drake</name></author><published>2018-07-02T10:08:41-04:00</published><updated>2018-07-02T10:08:41-04:00</updated><content type="html"><![CDATA[<p>The highest-performing engineering teams share one critical characteristic: they&rsquo;ve mastered rapid feedback loops. While many organizations talk about continuous improvement, few implement the systematic feedback mechanisms that make it possible.</p>
<p>The difference between teams that ship quality software consistently and those that struggle with technical debt and missed deadlines comes down to how quickly they can observe, learn, and adjust their approach. As an engineering leader, your job isn&rsquo;t to be the source of all feedback—it&rsquo;s to build systems that enable your team to continuously improve themselves.</p>
<h2 id="the-engineering-leadership-ooda-loop">The Engineering Leadership OODA Loop</h2>
<p>United States Air Force Colonel John Boyd developed the concept of the <a href="https://en.wikipedia.org/wiki/OODA_loop">OODA loop</a>, OODA being an acronym for <strong>observe, orient, decide, act</strong>. While originally designed for military strategy, this framework translates perfectly to engineering team leadership:</p>
<ul>
<li><strong>Observe:</strong> Gather data about team performance, code quality, delivery metrics, and team dynamics</li>
<li><strong>Orient:</strong> Analyze this information in the context of team goals, organizational constraints, and previous experience</li>
<li><strong>Decide:</strong> Choose specific interventions or changes to improve team performance</li>
<li><strong>Act:</strong> Implement these changes and measure their impact</li>
</ul>
<p>The power of the OODA loop for engineering leaders is in its emphasis on speed. Teams that can observe problems, orient around solutions, decide on actions, and act quickly will consistently outperform teams with slower feedback cycles. I&rsquo;ve seen engineering teams transform their delivery speed and quality by implementing systematic OODA loops at multiple levels: individual developer growth, code review processes, sprint retrospectives, and quarterly team health assessments.</p>
<h2 id="high-performance-team-feedback-systems">High-Performance Team Feedback Systems</h2>
<p>The most effective engineering teams I&rsquo;ve led implement feedback loops at multiple time scales. Here&rsquo;s what a comprehensive feedback system looks like:</p>
<p><strong>Daily feedback (hours):</strong></p>
<ul>
<li>Morning standup with updates on blockers and progress</li>
<li>Real-time pair programming and code review</li>
<li>Continuous integration feedback from automated tests</li>
<li>End-of-day team sync on tomorrow&rsquo;s priorities</li>
</ul>
<p><strong>Weekly feedback (days):</strong></p>
<ul>
<li>Sprint planning and backlog refinement</li>
<li>Code quality metrics review</li>
<li>Technical debt assessment</li>
<li>Team velocity and burndown analysis</li>
</ul>
<p><strong>Monthly feedback (weeks):</strong></p>
<ul>
<li>Sprint retrospectives with actions for improvements</li>
<li>Team health and satisfaction surveys</li>
<li>Architecture and technical direction discussions</li>
<li>Individual growth and career development conversations</li>
</ul>
<p><strong>Quarterly feedback (months):</strong></p>
<ul>
<li>Team performance against organizational goals</li>
<li>Process effectiveness and tooling evaluation</li>
<li>Long-term technical strategy adjustments</li>
<li>Team composition and skill gap analysis</li>
</ul>
<p>Each feedback loop serves a different purpose and operates at a different time scale. Your job as a leader is to ensure all these loops are functioning and feeding information up and down the hierarchy.</p>
<h2 id="building-team-feedback-culture">Building Team Feedback Culture</h2>
<p>Implementing effective feedback loops requires intentional leadership and systematic approach. Here&rsquo;s the framework I use to build high-performance engineering teams:</p>
<ol>
<li>Define clear, measurable team objectives</li>
<li>Create transparent planning and prioritization processes</li>
<li>Implement automation that provides rapid feedback</li>
<li>Build code review culture that accelerates learning</li>
<li>Set up regular process retrospectives</li>
<li>Close the loop: act on feedback systematically</li>
</ol>
<p>Each of these components reinforces the others, creating a self-improving system where the team becomes increasingly effective at identifying problems and implementing solutions.</p>
<h3 id="1-define-clear-measurable-team-objectives">1. Define Clear, Measurable Team Objectives</h3>
<p>Effective feedback loops require clear success criteria. Without concrete objectives, your team will struggle to know whether their improvements are actually working. As an engineering leader, you need to translate business goals into specific, measurable engineering outcomes.</p>
<ul>
<li><strong>Technical objectives:</strong> Delivery commitments with specific scope and timelines, quality metrics (bug rates, test coverage, performance benchmarks), and technical debt reduction goals with measurable impact</li>
<li><strong>Process objectives:</strong> Sprint velocity and predictability targets, code review turnaround time improvements, and deployment frequency and reliability goals</li>
<li><strong>Team health objectives:</strong> Individual skill development milestones, team satisfaction and engagement metrics, and knowledge sharing and documentation goals</li>
</ul>
<p>Make these objectives visible and regularly review progress using dashboards, team ceremonies, and one-on-one conversations. Treat objectives as hypotheses to test, not contracts to fulfill at all costs. When feedback indicates an objective is no longer relevant or achievable, adjust it.</p>
<h3 id="2-create-transparent-planning-and-prioritization-processes">2. Create Transparent Planning and Prioritization Processes</h3>
<p>High-performance teams excel at breaking down complex objectives into manageable, measurable work streams. This decomposition serves two purposes: it makes work achievable and it creates multiple feedback points where the team can course-correct.</p>
<ul>
<li><strong>Epic level (quarterly goals):</strong> Large initiatives that deliver significant business value, typically spanning 2-3 sprints. Example: &ldquo;Implement real-time collaboration features&rdquo;</li>
<li><strong>Story level (sprint goals):</strong> Deliverable features that can be completed within a sprint. Example: &ldquo;Users can see live cursor positions of other editors&rdquo;</li>
<li><strong>Task level (daily progress):</strong> Specific implementation work that can be completed in 1-2 days. Example: &ldquo;Implement WebSocket connection handling for cursor events&rdquo;</li>
</ul>
<p>Create feedback loops at each level:</p>
<ul>
<li><strong>Daily standups</strong> surface task-level blockers and progress</li>
<li><strong>Sprint reviews</strong> evaluate story completion and quality</li>
<li><strong>Quarterly planning</strong> assesses epic success and organizational alignment</li>
</ul>
<p>Teams perform best when planning is collaborative, transparent, and regularly revisited. Use tools like story mapping sessions, planning poker, and retrospective-driven backlog refinement to ensure the whole team understands and contributes to prioritization decisions. Treat plans as living documents that adjust quickly when feedback indicates priorities should shift.</p>
<h3 id="3-implement-automation-that-provides-rapid-feedback">3. Implement Automation That Provides Rapid Feedback</h3>
<p>Automation is critical for high-performance teams because it accelerates feedback loops and eliminates sources of inconsistency and error. Automation creates systems that provide immediate, reliable information about code quality and system health.</p>
<ul>
<li><strong>Immediate feedback (seconds to minutes):</strong> Pre-commit hooks that run tests, IDE integrations, and linting tools that enforce consistent standards</li>
<li><strong>Short-term feedback (minutes to hours):</strong> Continuous integration pipelines, automated security scanning, performance regression testing, and automated deployment to staging environments</li>
<li><strong>Medium-term feedback (hours to days):</strong> Automated monitoring and alerting for production systems, code quality metrics tracking, and performance monitoring alerts</li>
</ul>
<p>The key principle is &ldquo;shift left&rdquo;: catch problems as early as possible in the development cycle when they&rsquo;re cheaper and easier to fix. Start by documenting manual processes that the team repeats regularly, then prioritize automation based on frequency of use and the consequences of human error. The automation itself becomes a team learning exercise and creates shared ownership of the development process.</p>
<h3 id="4-build-code-review-culture-that-accelerates-learning">4. Build Code Review Culture That Accelerates Learning</h3>
<p>Code review is one of the most powerful feedback mechanisms available to engineering teams, but only when implemented as a learning and collaboration tool. High-performance teams use code review to accelerate knowledge transfer, maintain quality standards, and continuously improve their collective skills.</p>
<ul>
<li><strong>Establish clear expectations:</strong> Code review is required for all changes, should focus on code quality (not personal preferences), and both author and reviewer are responsible for the final quality</li>
<li><strong>Optimize for speed and quality:</strong> Target 24-hour turnaround time for initial review feedback, use automated tools to catch style issues, and provide specific, actionable feedback with examples</li>
<li><strong>Make reviews educational:</strong> Encourage questions and explanations in review comments, share alternative approaches and best practices, and rotate reviewers to spread knowledge across the team</li>
<li><strong>Measure and improve:</strong> Track review turnaround time and iteration cycles, monitor review feedback patterns to identify training opportunities, and regularly discuss review process effectiveness in retrospectives</li>
</ul>
<p>Create a culture where developers look forward to code review because they know they&rsquo;ll learn something and improve the overall codebase quality. When done well, code review becomes one of your most effective tools for maintaining technical standards and building team expertise.</p>
<h4 id="team-code-review-standards">Team Code Review Standards</h4>
<p>Here&rsquo;s the code review checklist I use with engineering teams. Adapt it collaboratively with your team to ensure buy-in and relevance to your specific context:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-markdown" data-lang="markdown"><span style="display:flex;"><span># Team Code Review Standards
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="font-weight:bold">**Functionality &amp; Requirements**</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">- [ ]</span> Implementation matches acceptance criteria and specifications
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">- [ ]</span> Edge cases and error conditions are properly handled
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">- [ ]</span> Changes are complete and don&#39;t break existing functionality
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">- [ ]</span> Performance impact has been considered and tested
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="font-weight:bold">**Code Quality &amp; Maintainability**</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">- [ ]</span> Code is readable and well-structured
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">- [ ]</span> Variable and function names clearly express intent
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">- [ ]</span> Complex logic is documented with comments
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">- [ ]</span> Code follows team style guidelines and patterns
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">- [ ]</span> No duplicate code or overly complex functions
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="font-weight:bold">**Testing &amp; Reliability**</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">- [ ]</span> Appropriate tests are included and pass
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">- [ ]</span> Test coverage meets team standards
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">- [ ]</span> Manual testing has been performed where applicable
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">- [ ]</span> Changes don&#39;t introduce security vulnerabilities
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="font-weight:bold">**Team Collaboration**</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">- [ ]</span> Pull request description clearly explains the change
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">- [ ]</span> Related documentation has been updated
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">- [ ]</span> Breaking changes are clearly communicated
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">- [ ]</span> Knowledge sharing opportunities have been identified
</span></span></code></pre></div><p>Make this checklist a living document that evolves based on team retrospectives and lessons learned. Regularly review and update the standards based on what issues you&rsquo;re catching (or missing) in production.</p>
<h3 id="5-set-up-regular-process-retrospectives">5. Set Up Regular Process Retrospectives</h3>
<p>Process retrospectives are where teams close the feedback loop by systematically improving how they work. High-performance teams treat retrospectives as their most important ceremony because it&rsquo;s where all other improvements originate. The most effective retrospectives happen at multiple cadences:</p>
<ul>
<li><strong>Sprint retrospectives (every 1-2 weeks):</strong> Focus on immediate process improvements and team dynamics using formats like &ldquo;Start, Stop, Continue&rdquo;</li>
<li><strong>Quarterly team health reviews:</strong> Deeper dive into team effectiveness, skill development, and strategic alignment with quantitative analysis of delivery metrics</li>
<li><strong>Post-incident reviews:</strong> Blameless analysis of production issues focusing on system improvements rather than individual accountability</li>
<li><strong>Process optimization sessions:</strong> Dedicated time to review and improve specific workflows like deployment, testing, or code review processes</li>
</ul>
<h4 id="effective-retrospective-framework">Effective Retrospective Framework</h4>
<p>Here are the key questions I use to guide productive team retrospectives:</p>
<ul>
<li><strong>Team performance review:</strong> How did we perform against our objectives? What factors contributed to successes? What blockers slowed us down? How effectively did our processes support our goals?</li>
<li><strong>Process effectiveness analysis:</strong> Which practices are working well? What processes are creating friction or waste? Where are we spending time on work that doesn&rsquo;t create value? What automation would have the biggest impact?</li>
<li><strong>Team health and growth:</strong> How well are we collaborating and communicating? What skills or knowledge gaps are limiting our effectiveness? Are team members feeling challenged and supported in their growth?</li>
<li><strong>Forward-looking improvements:</strong> What are the top 2-3 experiments we want to try next period? How will we measure success of these changes? What obstacles do we anticipate and how can we prepare for them?</li>
</ul>
<p>Make retrospectives action-oriented. Every retrospective should end with specific commitments about what the team will try differently, who will own those changes, and how success will be measured.</p>
<h3 id="6-close-the-loop-act-on-feedback-systematically">6. Close the Loop: Act on Feedback Systematically</h3>
<p>The most critical step in building high-performance teams is ensuring that feedback actually drives change. Many teams collect feedback but fail to systematically implement improvements. This is where engineering leadership makes the biggest difference.</p>
<ul>
<li><strong>Make changes visible and trackable:</strong> Document all process experiments and improvements in a shared space, track metrics before and after implementing changes, and celebrate successful improvements publicly to reinforce the feedback culture</li>
<li><strong>Create accountability for implementation:</strong> Assign owners for each improvement initiative, set specific timelines and success criteria, and review progress on improvements in regular team meetings</li>
<li><strong>Build improvement into regular workflow:</strong> Allocate dedicated time for process improvement work, include improvement tasks in sprint planning, and make process improvement a regular topic in one-on-one conversations</li>
<li><strong>Scale successful practices:</strong> Share effective improvements with other teams in the organization, document successful patterns for future reference, and build successful practices into onboarding for new team members</li>
</ul>
<p>Create a self-reinforcing cycle where the team becomes increasingly effective at identifying problems, implementing solutions, and measuring results. Teams that master this cycle become engines of continuous improvement that consistently outperform their peers.</p>
<p>Building high-performance engineering teams through feedback loops requires patience, consistency, and commitment from leadership. The investment pays enormous dividends in team velocity, code quality, job satisfaction, and organizational impact.</p>
]]></content></entry><entry><title type="html">Adorable bookmarklets want to help delete your social media data</title><link href="https://victoria.dev/archive/adorable-bookmarklets-want-to-help-delete-your-social-media-data/"/><id>https://victoria.dev/archive/adorable-bookmarklets-want-to-help-delete-your-social-media-data/</id><author><name>Victoria Drake</name></author><published>2018-06-14T13:12:02-04:00</published><updated>2018-06-14T13:12:02-04:00</updated><content type="html"><![CDATA[<p>A little while ago I wrote about a Lambda function I called ephemeral for deleting my old tweets. While it&rsquo;s a great project for someone familiar with or wanting to learn to use Lambda, it isn&rsquo;t simple for a non-technical person to set up. There are services out there that will delete your tweets for you, but require your access credentials. There didn&rsquo;t seem to be anything that provided convenience without also requiring authentication.</p>
<p>So, I went oldschool and created the ephemeral bookmarklet.</p>
<p>If that didn&rsquo;t make you instantly nostalgic, a <a href="https://en.wikipedia.org/wiki/Bookmarklet">bookmarklet</a> is a little application that lives as a bookmark in your web browser. You &ldquo;install&rdquo; it by dragging the link to your bookmarks toolbar, or right-clicking on the link and choosing &ldquo;Bookmark this link&rdquo; (Firefox). You click it to execute the program on the current page.</p>
<p>Here&rsquo;s what the ephemeral bookmarklet will do:</p>
<video width="600px" controls="controls" poster="bookmarklet-card.png">
    <source src="ephemeralbookmarklet.mp4" type="video/mp4" />
</video>
<p>The ephemeral bookmarklet is part of a new suite of tools for personal data management that I&rsquo;m co-creating with Adam Drake. You can <a href="https://adamdrake.github.io/pdmtools/">get all the bookmarklets on this page</a>, and they&rsquo;re also open source <a href="https://github.com/adamdrake/pdmtools">on GitHub</a>.</p>
<p>There are currently bookmarklets for managing your data on LinkedIn and Twitter. We&rsquo;re looking for testers and contributors to help make this a comprehensive toolset for your social media data management. If you write code, I invite you to contribute and help this toolset grow.</p>
<p>∩{｡◕‿◕｡}∩ &ndash; Bookmarklet says hi!</p>
]]></content></entry><entry><title type="html">A coffee-break introduction to time complexity of algorithms</title><link href="https://victoria.dev/archive/a-coffee-break-introduction-to-time-complexity-of-algorithms/"/><id>https://victoria.dev/archive/a-coffee-break-introduction-to-time-complexity-of-algorithms/</id><author><name>Victoria Drake</name></author><published>2018-05-30T14:08:28-04:00</published><updated>2018-05-30T14:08:28-04:00</updated><content type="html"><![CDATA[<p>Just like writing your very first <code>for</code> loop, understanding time complexity is an integral milestone to learning how to write efficient complex programs. Think of it as having a superpower that allows you to know exactly what type of program might be the most efficient in a particular situation - before even running a single line of code.</p>
<p>The fundamental concepts of complexity analysis are well worth studying. You&rsquo;ll be able to better understand how the code you&rsquo;re writing will interact with the program&rsquo;s input, and as a result, you&rsquo;ll spend a lot less wasted time writing slow and problematic code. It won&rsquo;t take long to go over all you need to know in order to start writing more efficient programs - in fact, we can do it in about fifteen minutes. You can go grab a coffee right now (or tea, if that&rsquo;s your thing) and I&rsquo;ll take you through it before your coffee break is over. Go ahead, I&rsquo;ll wait.</p>
<p>All set? Let&rsquo;s do it!</p>
<h2 id="what-is-time-complexity-anyway">What is &ldquo;time complexity&rdquo; anyway</h2>
<p>The time complexity of an algorithm is an <strong>approximation</strong> of how long that algorithm will take to process some input. It describes the efficiency of the algorithm by the magnitude of its operations. This is different than the number of times an operation repeats; I&rsquo;ll expand on that later. Generally, the fewer operations the algorithm has, the faster it will be.</p>
<p>We write about time complexity using <a href="https://en.wikipedia.org/wiki/Big_O_notation">Big O notation</a>, which looks something like <em>O</em>(<em>n</em>). There&rsquo;s rather a lot of math involved in its formal definition, but informally we can say that Big O notation gives us our algorithm&rsquo;s approximate run time in the <strong>worst case</strong>, or in other words, its upper bound.<sup>[<a href="#references">2</a>]</sup> It is inherently relative and comparative.<sup>[<a href="#references">3</a>]</sup> We&rsquo;re describing the algorithm&rsquo;s efficiency relative to the increasing size of its input data, <em>n</em>. If the input is a string, then <em>n</em> is the length of the string. If it&rsquo;s a list of integers, <em>n</em> is the length of the list.</p>
<p>It&rsquo;s easiest to picture what Big O notation represents with a graph:</p>
<figure class="screenshot"><img src="/archive/a-coffee-break-introduction-to-time-complexity-of-algorithms/graph.png"
    alt="A graph showing different classes of time complexity"><figcaption>
      <p>Lines made with the very excellent Desmos graph calculator. You can <a href="https://www.desmos.com/calculator/xpfyjl1lbn">play with this graph here</a>.</p>
    </figcaption>
</figure>

<p>Here are the main important points to remember as you read the rest of this article:</p>
<ul>
<li>Time complexity is an approximation</li>
<li>An algorithm&rsquo;s time complexity approximates its worst case run time</li>
</ul>
<h2 id="determining-time-complexity">Determining time complexity</h2>
<p>There are different classes of complexity that we can use to quickly understand an algorithm. I&rsquo;ll illustrate some of these classes using nested loops and other examples.</p>
<h2 id="polynomial-time-complexity">Polynomial time complexity</h2>
<p>A <strong>polynomial</strong>, from the Greek <em>poly</em> meaning &ldquo;many,&rdquo; and Latin <em>nomen</em> meaning &ldquo;name,&rdquo; describes an expression comprised of constant variables, and addition, multiplication, and exponentiation to a non-negative integer power.<sup>[<a href="#references">4</a>]</sup> That&rsquo;s a super math-y way to say that it contains variables usually denoted by letters and symbols that look like these:</p>
<p><img src="polynomial.png" alt="A polynomial example"></p>
<p>The below classes describe polynomial algorithms. Some have food examples.</p>
<h3 id="constant">Constant</h3>
<p>A <strong>constant time</strong> algorithm doesn&rsquo;t change its running time in response to the input data. No matter the size of the data it receives, the algorithm takes the same amount of time to run. We denote this as a time complexity of <em>O</em>(1).</p>
<figure class="screenshot"><img src="/archive/a-coffee-break-introduction-to-time-complexity-of-algorithms/graph%281%29.png"
    alt="A graph showing constant time complexity.">
</figure>

<p>Here&rsquo;s one example of a constant algorithm that takes the first item in a slice.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">takeCupcake</span>(<span style="color:#a6e22e">cupcakes</span> []<span style="color:#66d9ef">int</span>) <span style="color:#66d9ef">int</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">cupcakes</span>[<span style="color:#ae81ff">0</span>]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><figure><img src="/archive/a-coffee-break-introduction-to-time-complexity-of-algorithms/cupcakes.png"
    alt="Types of cupcakes"><figcaption>
      <p>Choice of flavours are: vanilla cupcake, strawberry cupcake, mint chocolate cupcake, lemon cupcake, and wibbly wobbly, timey wimey cupcake.</p>
    </figcaption>
</figure>

<p>With this constant-time algorithm, no matter how many cupcakes are on offer, you just get the first one. Oh well. Flavours are overrated anyway.</p>
<h3 id="linear">Linear</h3>
<p>The running duration of a <strong>linear</strong> algorithm is constant. It will process the input in <em>n</em> number of operations. This is often the best possible (most efficient) case for time complexity where all the data must be examined.</p>
<figure class="screenshot"><img src="/archive/a-coffee-break-introduction-to-time-complexity-of-algorithms/graph%28n%29.png"
    alt="A graph showing linear time complexity.">
</figure>

<p>Here&rsquo;s an example of code with time complexity of <em>O</em>(<em>n</em>):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">eatChips</span>(<span style="color:#a6e22e">bowlOfChips</span> <span style="color:#66d9ef">int</span>) {
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">chip</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">chip</span> <span style="color:#f92672">&lt;=</span> <span style="color:#a6e22e">bowlOfChips</span>; <span style="color:#a6e22e">chip</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// dip chip
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Here&rsquo;s another example of code with time complexity of <em>O</em>(<em>n</em>):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">eatChips</span>(<span style="color:#a6e22e">bowlOfChips</span> <span style="color:#66d9ef">int</span>) {
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">chip</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">chip</span> <span style="color:#f92672">&lt;=</span> <span style="color:#a6e22e">bowlOfChips</span>; <span style="color:#a6e22e">chip</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// double dip chip
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>It doesn&rsquo;t matter whether the code inside the loop executes once, twice, or any number of times. Both these loops process the input by a constant factor of <em>n</em>, and thus can be described as linear.</p>
<figure><img src="/archive/a-coffee-break-introduction-to-time-complexity-of-algorithms/dip.png"
    alt="Lifeguard MIQ the chip says no double dipping"><figcaption>
      <p>Don&rsquo;t double dip in a shared bowl.</p>
    </figcaption>
</figure>

<h3 id="quadratic">Quadratic</h3>
<figure class="screenshot"><img src="/archive/a-coffee-break-introduction-to-time-complexity-of-algorithms/graph%28n2%29.png"
    alt="A graph showing quadratic time complexity">
</figure>

<p>Now here&rsquo;s an example of code with time complexity of <em>O</em>(<em>n</em><sup>2</sup>):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">pizzaDelivery</span>(<span style="color:#a6e22e">pizzas</span> <span style="color:#66d9ef">int</span>) {
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">pizza</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">pizza</span> <span style="color:#f92672">&lt;=</span> <span style="color:#a6e22e">pizzas</span>; <span style="color:#a6e22e">pizza</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// slice pizza
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">slice</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">slice</span> <span style="color:#f92672">&lt;=</span> <span style="color:#a6e22e">pizza</span>; <span style="color:#a6e22e">slice</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>   <span style="color:#75715e">// eat slice of pizza
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  }
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Because there are two nested loops, or nested linear operations, the algorithm process the input <em>n</em><sup>2</sup> times.</p>
<h3 id="cubic">Cubic</h3>
<figure class="screenshot"><img src="/archive/a-coffee-break-introduction-to-time-complexity-of-algorithms/graph%28n3%29.png"
    alt="A graph showing cubic time complexity">
</figure>

<p>Extending on the previous example, this code with three nested loops has time complexity of <em>O</em>(<em>n</em><sup>3</sup>):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">pizzaDelivery</span>(<span style="color:#a6e22e">boxesDelivered</span> <span style="color:#66d9ef">int</span>) {
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">pizzaBox</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">pizzaBox</span> <span style="color:#f92672">&lt;=</span> <span style="color:#a6e22e">boxesDelivered</span>; <span style="color:#a6e22e">pizzaBox</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// open box
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">pizza</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">pizza</span> <span style="color:#f92672">&lt;=</span> <span style="color:#a6e22e">pizzaBox</span>; <span style="color:#a6e22e">pizza</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>   <span style="color:#75715e">// slice pizza
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>   <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">slice</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">slice</span> <span style="color:#f92672">&lt;=</span> <span style="color:#a6e22e">pizza</span>; <span style="color:#a6e22e">slice</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// eat slice of pizza
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>   }
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><figure><img src="/archive/a-coffee-break-introduction-to-time-complexity-of-algorithms/unsliced.png"
    alt="A pizza pie in a box with a pizza slicer dependency"><figcaption>
      <p>Seriously though, who delivers unsliced pizza??</p>
    </figcaption>
</figure>

<h3 id="logarithmic">Logarithmic</h3>
<p>A <strong>logarithmic</strong> algorithm is one that reduces the size of the input at every step.
We denote this time complexity as <em>O</em>(log <em>n</em>), where <strong>log</strong>, the logarithm function, is this shape:</p>
<figure class="screenshot"><img src="/archive/a-coffee-break-introduction-to-time-complexity-of-algorithms/graph%28logn%29.png"
    alt="A graph showing logarithmic time complexity">
</figure>

<p>One example of this is a <a href="https://en.wikipedia.org/wiki/Binary_search_algorithm">binary search algorithm</a> that finds the position of an element within a sorted array. Here&rsquo;s how it would work, assuming we&rsquo;re trying to find the element <em>x</em>:</p>
<ol>
<li>If <em>x</em> matches the middle element <em>m</em> of the array, return the position of <em>m</em></li>
<li>If <em>x</em> doesn&rsquo;t match <em>m</em>, see if <em>m</em> is larger or smaller than <em>x</em>
<ul>
<li>If larger, discard all array items greater than <em>m</em></li>
<li>If smaller, discard all array items smaller than <em>m</em></li>
</ul>
</li>
<li>Continue by repeating steps 1 and 2 on the remaining array until <em>x</em> is found</li>
</ol>
<p>I find the clearest analogy for understanding binary search is imagining the process of locating a book in a bookstore aisle. If the books are organized by author&rsquo;s last name and you want to find &ldquo;Terry Pratchett,&rdquo; you know you need to look for the &ldquo;P&rdquo; section.</p>
<p>You can approach the shelf at any point along the aisle and look at the author&rsquo;s last name there. If you&rsquo;re looking at a book by Neil Gaiman, you know you can ignore all the rest of the books to your left, since no letters that come before &ldquo;G&rdquo; in the alphabet happen to be &ldquo;P.&rdquo; You would then move down the aisle to the right any amount, and repeat this process until you&rsquo;ve found the Terry Pratchett section, which should be rather sizable if you&rsquo;re at any decent bookstore because wow did he write a lot of books.</p>
<h3 id="quasilinear">Quasilinear</h3>
<figure class="screenshot"><img src="/archive/a-coffee-break-introduction-to-time-complexity-of-algorithms/graph%28nlogn%29.png"
    alt="A graph showing quasilinear time complexity">
</figure>

<p>Often seen with sorting algorithms, the time complexity <em>O</em>(<em>n</em> log <em>n</em>) can describe a data structure where each operation takes <em>O</em>(log <em>n</em>) time. One example of this is <a href="https://en.wikipedia.org/wiki/Quicksort">quick sort</a>, a divide-and-conquer algorithm.</p>
<p>Quick sort works by dividing up an unsorted array into smaller chunks that are easier to process. It sorts the sub-arrays, and thus the whole array. Think about it like trying to put a deck of cards in order. It&rsquo;s faster if you split up the cards and get five friends to help you.</p>
<h3 id="non-polynomial-time-complexity">Non-polynomial time complexity</h3>
<p>The below classes of algorithms are non-polynomial.</p>
<h3 id="factorial">Factorial</h3>
<figure class="screenshot"><img src="/archive/a-coffee-break-introduction-to-time-complexity-of-algorithms/graph%28nfac%29.png"
    alt="A graph showing factorial time complexity">
</figure>

<p>An algorithm with time complexity <em>O</em>(<em>n</em>!) often iterates through all permutations of the input elements. One common example is a <a href="https://en.wikipedia.org/wiki/Brute-force_search">brute-force search</a> seen in the <a href="https://en.wikipedia.org/wiki/Travelling_salesman_problem#Computing_a_solution">travelling salesman problem</a>. It tries to find the least costly path between a number of points by enumerating all possible permutations and finding the ones with the lowest cost.</p>
<h3 id="exponential">Exponential</h3>
<p>An <strong>exponential</strong> algorithm often also iterates through all subsets of the input elements. It is denoted <em>O</em>(2<sup><em>n</em></sup>) and is often seen in brute-force algorithms. It is similar to factorial time except in its rate of growth, which as you may not be surprised to hear, is exponential. The larger the data set, the more steep the curve becomes.</p>
<figure class="screenshot"><img src="/archive/a-coffee-break-introduction-to-time-complexity-of-algorithms/graph%282n%29.png"
    alt="A graph showing exponential time complexity">
</figure>

<p>In cryptography, a brute-force attack may systematically check all possible elements of a password by iterating through subsets. Using an exponential algorithm to do this, it becomes incredibly resource-expensive to brute-force crack a long password versus a shorter one. This is one reason that a long password is considered more secure than a shorter one.</p>
<p>There are further time complexity classes less commonly seen that I won&rsquo;t cover here, but you can read about these and find examples in <a href="https://en.wikipedia.org/wiki/Time_complexity#Table_of_common_time_complexities">this handy table</a>.</p>
<h3 id="recursion-time-complexity">Recursion time complexity</h3>
<p>As I described in my article <a href="/blog/understanding-array.prototype.reduce-and-recursion-using-apple-pie/">explaining recursion using apple pie</a>, a recursive function calls itself under specified conditions. Its time complexity depends on how many times the function is called and the time complexity of a single function call. In other words, it&rsquo;s the product of the number of times the function runs and a single execution&rsquo;s time complexity.</p>
<p>Here&rsquo;s a recursive function that eats pies until no pies are left:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">eatPies</span>(<span style="color:#a6e22e">pies</span> <span style="color:#66d9ef">int</span>) <span style="color:#66d9ef">int</span> {
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">pies</span> <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">pies</span>
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">eatPies</span>(<span style="color:#a6e22e">pies</span> <span style="color:#f92672">-</span> <span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The time complexity of a single execution is constant. No matter how many pies are input, the program will do the same thing: check to see if the input is 0. If so, return, and if not, call itself with one fewer pie.</p>
<p>The initial number of pies could be any number, and we need to process all of them, so we can describe the input as <em>n</em>. Thus, the time complexity of this recursive function is the product <em>O</em>(<em>n</em>).</p>
<figure><img src="/archive/a-coffee-break-introduction-to-time-complexity-of-algorithms/piespile.png"
    alt="A pile of pizza boxes with pies to be eaten"><figcaption>
      <p>This function&rsquo;s return value is zero, plus some indigestion.</p>
    </figcaption>
</figure>

<h3 id="worst-case-time-complexity">Worst case time complexity</h3>
<p>So far, we&rsquo;ve talked about the time complexity of a few nested loops and some code examples. Most algorithms, however, are built from many combinations of these. How do we determine the time complexity of an algorithm containing many of these elements strung together?</p>
<p>Easy. We can describe the total time complexity of the algorithm by finding the largest complexity among all of its parts. This is because the slowest part of the code is the bottleneck, and time complexity is concerned with describing the worst case for the algorithm&rsquo;s run time.</p>
<p>Say we have a program for an office party. If our program looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> <span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">takeCupcake</span>(<span style="color:#a6e22e">cupcakes</span> []<span style="color:#66d9ef">int</span>) <span style="color:#66d9ef">int</span> {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;Have cupcake number&#34;</span>,<span style="color:#a6e22e">cupcakes</span>[<span style="color:#ae81ff">0</span>])
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">cupcakes</span>[<span style="color:#ae81ff">0</span>]
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">eatChips</span>(<span style="color:#a6e22e">bowlOfChips</span> <span style="color:#66d9ef">int</span>) {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;Have some chips!&#34;</span>)
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">chip</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">chip</span> <span style="color:#f92672">&lt;=</span> <span style="color:#a6e22e">bowlOfChips</span>; <span style="color:#a6e22e">chip</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// dip chip
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> }
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;No more chips.&#34;</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">pizzaDelivery</span>(<span style="color:#a6e22e">boxesDelivered</span> <span style="color:#66d9ef">int</span>) {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;Pizza is here!&#34;</span>)
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">pizzaBox</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">pizzaBox</span> <span style="color:#f92672">&lt;=</span> <span style="color:#a6e22e">boxesDelivered</span>; <span style="color:#a6e22e">pizzaBox</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// open box
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">pizza</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">pizza</span> <span style="color:#f92672">&lt;=</span> <span style="color:#a6e22e">pizzaBox</span>; <span style="color:#a6e22e">pizza</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>   <span style="color:#75715e">// slice pizza
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>   <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">slice</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">slice</span> <span style="color:#f92672">&lt;=</span> <span style="color:#a6e22e">pizza</span>; <span style="color:#a6e22e">slice</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// eat slice of pizza
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>   }
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;Pizza is gone.&#34;</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">eatPies</span>(<span style="color:#a6e22e">pies</span> <span style="color:#66d9ef">int</span>) <span style="color:#66d9ef">int</span> {
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">pies</span> <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;Someone ate all the pies!&#34;</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">pies</span>
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;Eating pie...&#34;</span>)
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">eatPies</span>(<span style="color:#a6e22e">pies</span> <span style="color:#f92672">-</span> <span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">takeCupcake</span>([]<span style="color:#66d9ef">int</span>{<span style="color:#ae81ff">1</span>, <span style="color:#ae81ff">2</span>, <span style="color:#ae81ff">3</span>})
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">eatChips</span>(<span style="color:#ae81ff">23</span>)
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">pizzaDelivery</span>(<span style="color:#ae81ff">3</span>)
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">eatPies</span>(<span style="color:#ae81ff">3</span>)
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;Food gone. Back to work!&#34;</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>We can describe the time complexity of all the code by the complexity of its most complex part. This program is made up of functions we&rsquo;ve already seen, with the following time complexity classes:</p>
<table>
<thead>
<tr>
<th>Function</th>
<th>Class</th>
<th>Big O</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>takeCupcake</code></td>
<td>constant</td>
<td><em>O</em>(1)</td>
</tr>
<tr>
<td><code>eatChips</code></td>
<td>linear</td>
<td><em>O</em>(<em>n</em>)</td>
</tr>
<tr>
<td><code>pizzaDelivery</code></td>
<td>cubic</td>
<td><em>O</em>(<em>n</em><sup>3</sup>)</td>
</tr>
<tr>
<td><code>eatPies</code></td>
<td>linear (recursive)</td>
<td><em>O</em>(<em>n</em>)</td>
</tr>
</tbody>
</table>
<p>To describe the time complexity of the entire office party program, we choose the worst case. This program would have the time complexity <em>O</em>(<em>n</em><sup>3</sup>).</p>
<p>Here&rsquo;s the office party soundtrack, just for fun.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>Have cupcake number <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>Have some chips!
</span></span><span style="display:flex;"><span>No more chips.
</span></span><span style="display:flex;"><span>Pizza is here!
</span></span><span style="display:flex;"><span>Pizza is gone.
</span></span><span style="display:flex;"><span>Eating pie...
</span></span><span style="display:flex;"><span>Eating pie...
</span></span><span style="display:flex;"><span>Eating pie...
</span></span><span style="display:flex;"><span>Someone ate all the pies!
</span></span><span style="display:flex;"><span>Food gone. Back to work!
</span></span></code></pre></div><h2 id="p-vs-np-np-complete-and-np-hard">P vs NP, NP-complete, and NP-hard</h2>
<p>You may come across these terms in your explorations of time complexity. Informally, <strong>P</strong> (for Polynomial time), is a class of problems that is quick to solve. <strong>NP</strong>, for Nondeterministic Polynomial time, is a class of problems where the answer can be quickly verified in polynomial time. NP encompasses P, but also another class of problems called <strong>NP-complete</strong>, for which no fast solution is known.<sup>[<a href="#references">5</a>]</sup> Outside of NP but still including NP-complete is yet another class called <strong>NP-hard</strong>, which includes problems that no one has been able to verifiably solve with polynomial algorithms.<sup>[<a href="#references">6</a>]</sup></p>
<figure class="screenshot"><img src="/archive/a-coffee-break-introduction-to-time-complexity-of-algorithms/pnpeuler.svg"
    alt="Euler diagram"><figcaption>
      <p>P vs NP Euler diagram, <a href="https://commons.wikimedia.org/w/index.php?curid=3532181">by Behnam Esfahbod, CC BY-SA 3.0</a></p>
    </figcaption>
</figure>

<p><a href="https://en.wikipedia.org/wiki/P_versus_NP_problem">P versus NP</a> is an unsolved, open question in computer science.</p>
<p>Anyway, you don&rsquo;t generally need to know about NP and NP-hard problems to begin taking advantage of understanding time complexity. They&rsquo;re a whole other Pandora&rsquo;s box.</p>
<h2 id="approximate-the-efficiency-of-an-algorithm-before-you-write-the-code">Approximate the efficiency of an algorithm before you write the code</h2>
<p>So far, we&rsquo;ve identified some different time complexity classes and how we might determine which one an algorithm falls into. So how does this help us before we&rsquo;ve written any code to evaluate?</p>
<p>By combining a little knowledge of time complexity with an awareness of the size of our input data, we can take a guess at an efficient algorithm for processing our data within a given time constraint. We can base our estimation on the fact that a modern computer can perform some hundreds of millions of operations in a second.<sup>[<a href="#references">1</a>]</sup> The following table from the <a href="#references">Competitive Programmer&rsquo;s Handbook</a> offers some estimates on required time complexity to process the respective input size in a time limit of one second.</p>
<table>
<thead>
<tr>
<th>Input size</th>
<th>Required time complexity for 1s processing time</th>
</tr>
</thead>
<tbody>
<tr>
<td>n ≤ 10</td>
<td><em>O</em>(<em>n</em>!)</td>
</tr>
<tr>
<td>n ≤ 20</td>
<td><em>O</em>(2<sup><em>n</em></sup>)</td>
</tr>
<tr>
<td>n ≤ 500</td>
<td><em>O</em>(<em>n</em><sup>3</sup>)</td>
</tr>
<tr>
<td>n ≤ 5000</td>
<td><em>O</em>(<em>n</em><sup>2</sup>)</td>
</tr>
<tr>
<td>n ≤ 10<sup>6</sup></td>
<td><em>O</em>(<em>n</em> log <em>n</em>) or <em>O</em>(<em>n</em>)</td>
</tr>
<tr>
<td>n is large</td>
<td><em>O</em>(1) or <em>O</em>(log <em>n</em>)</td>
</tr>
</tbody>
</table>
<p>Keep in mind that time complexity is an approximation, and not a guarantee. We can save a lot of time and effort by immediately ruling out algorithm designs that are unlikely to suit our constraints, but we must also consider that Big O notation doesn&rsquo;t account for <strong>constant factors</strong>. Here&rsquo;s some code to illustrate.</p>
<p>The following two algorithms both have <em>O</em>(<em>n</em>) time complexity.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">makeCoffee</span>(<span style="color:#a6e22e">scoops</span> <span style="color:#66d9ef">int</span>) {
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">scoop</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">scoop</span> <span style="color:#f92672">&lt;=</span> <span style="color:#a6e22e">scoops</span>; <span style="color:#a6e22e">scoop</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// add instant coffee
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">makeStrongCoffee</span>(<span style="color:#a6e22e">scoops</span> <span style="color:#66d9ef">int</span>) {
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">scoop</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">scoop</span> <span style="color:#f92672">&lt;=</span> <span style="color:#ae81ff">3</span><span style="color:#f92672">*</span><span style="color:#a6e22e">scoops</span>; <span style="color:#a6e22e">scoop</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// add instant coffee
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The first function makes a cup of coffee with the number of scoops we ask for. The second function also makes a cup of coffee, but it triples the number of scoops we ask for. To see an illustrative example, let&rsquo;s ask both these functions for a cup of coffee with a million scoops.</p>
<p>Here&rsquo;s the output of the Go test:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>Benchmark_makeCoffee-4          <span style="color:#ae81ff">1000000000</span>               0.29 ns/op
</span></span><span style="display:flex;"><span>Benchmark_makeStrongCoffee-4    <span style="color:#ae81ff">1000000000</span>               0.86 ns/op
</span></span></code></pre></div><p>Our first function, <code>makeCoffee</code>, completed in an average 0.29 nanoseconds. Our second function, <code>makeStrongCoffee</code>, completed in an average of 0.86 nanoseconds. While those may both seem like pretty small numbers, consider that the stronger coffee took near three times longer to make. This should make sense intuitively, since we asked it to triple the scoops. Big O notation alone wouldn&rsquo;t tell you this, since the constant factor of the tripled scoops isn&rsquo;t accounted for.</p>
<h2 id="improve-time-complexity-of-existing-code">Improve time complexity of existing code</h2>
<p>Becoming familiar with time complexity gives us the opportunity to write code, or refactor code, to be more efficient. To illustrate, I&rsquo;ll give a concrete example of one way we can refactor a bit of code to improve its time complexity.</p>
<p>Let&rsquo;s say a bunch of people at the office want some pie. Some people want pie more than others. The amount that everyone wants some pie is represented by an <code>int</code> &gt; 0:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">diners</span> <span style="color:#f92672">:=</span> []<span style="color:#66d9ef">int</span>{<span style="color:#ae81ff">2</span>, <span style="color:#ae81ff">88</span>, <span style="color:#ae81ff">87</span>, <span style="color:#ae81ff">16</span>, <span style="color:#ae81ff">42</span>, <span style="color:#ae81ff">10</span>, <span style="color:#ae81ff">34</span>, <span style="color:#ae81ff">1</span>, <span style="color:#ae81ff">43</span>, <span style="color:#ae81ff">56</span>}
</span></span></code></pre></div><p>Unfortunately, we&rsquo;re bootstrapped and there are only three forks to go around. Since we&rsquo;re a cooperative bunch, the three people who want pie the most will receive the forks to eat it with. Even though they&rsquo;ve all agreed on this, no one seems to want to sort themselves out and line up in an orderly fashion, so we&rsquo;ll have to make do with everybody jumbled about.</p>
<p>Without sorting the list of diners, return the three largest integers in the slice.</p>
<p>Here&rsquo;s a function that solves this problem and has <em>O</em>(<em>n</em><sup>2</sup>) time complexity:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">giveForks</span>(<span style="color:#a6e22e">diners</span> []<span style="color:#66d9ef">int</span>) []<span style="color:#66d9ef">int</span> {
</span></span><span style="display:flex;"><span> <span style="color:#75715e">// make a slice to store diners who will receive forks
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">withForks</span> []<span style="color:#66d9ef">int</span>
</span></span><span style="display:flex;"><span> <span style="color:#75715e">// loop over three forks
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">1</span>; <span style="color:#a6e22e">i</span> <span style="color:#f92672">&lt;=</span> <span style="color:#ae81ff">3</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// variables to keep track of the highest integer and where it is
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">max</span>, <span style="color:#a6e22e">maxIndex</span> <span style="color:#66d9ef">int</span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// loop over the diners slice
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">n</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">diners</span> {
</span></span><span style="display:flex;"><span>   <span style="color:#75715e">// if this integer is higher than max, update max and maxIndex
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>   <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">diners</span>[<span style="color:#a6e22e">n</span>] &gt; <span style="color:#a6e22e">max</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">max</span> = <span style="color:#a6e22e">diners</span>[<span style="color:#a6e22e">n</span>]
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">maxIndex</span> = <span style="color:#a6e22e">n</span>
</span></span><span style="display:flex;"><span>   }
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// remove the highest integer from the diners slice for the next loop
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#a6e22e">diners</span> = append(<span style="color:#a6e22e">diners</span>[:<span style="color:#a6e22e">maxIndex</span>], <span style="color:#a6e22e">diners</span>[<span style="color:#a6e22e">maxIndex</span><span style="color:#f92672">+</span><span style="color:#ae81ff">1</span>:]<span style="color:#f92672">...</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// keep track of who gets a fork
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#a6e22e">withForks</span> = append(<span style="color:#a6e22e">withForks</span>, <span style="color:#a6e22e">max</span>)
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">withForks</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>This program works, and eventually returns diners <code>[88 87 56]</code>. Everyone gets a little impatient while it&rsquo;s running though, since it takes rather a long time (about 120 nanoseconds) just to hand out three forks, and the pie&rsquo;s getting cold. How could we improve it?</p>
<p>By thinking about our approach in a slightly different way, we can refactor this program to have <em>O</em>(<em>n</em>) time complexity:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">giveForks</span>(<span style="color:#a6e22e">diners</span> []<span style="color:#66d9ef">int</span>) []<span style="color:#66d9ef">int</span> {
</span></span><span style="display:flex;"><span> <span style="color:#75715e">// make a slice to store diners who will receive forks
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">withForks</span> []<span style="color:#66d9ef">int</span>
</span></span><span style="display:flex;"><span> <span style="color:#75715e">// create variables for each fork
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">first</span>, <span style="color:#a6e22e">second</span>, <span style="color:#a6e22e">third</span> <span style="color:#66d9ef">int</span>
</span></span><span style="display:flex;"><span> <span style="color:#75715e">// loop over the diners
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">diners</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// assign the forks
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">diners</span>[<span style="color:#a6e22e">i</span>] &gt; <span style="color:#a6e22e">first</span> {
</span></span><span style="display:flex;"><span>   <span style="color:#a6e22e">third</span> = <span style="color:#a6e22e">second</span>
</span></span><span style="display:flex;"><span>   <span style="color:#a6e22e">second</span> = <span style="color:#a6e22e">first</span>
</span></span><span style="display:flex;"><span>   <span style="color:#a6e22e">first</span> = <span style="color:#a6e22e">diners</span>[<span style="color:#a6e22e">i</span>]
</span></span><span style="display:flex;"><span>  } <span style="color:#66d9ef">else</span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">diners</span>[<span style="color:#a6e22e">i</span>] &gt; <span style="color:#a6e22e">second</span> {
</span></span><span style="display:flex;"><span>   <span style="color:#a6e22e">third</span> = <span style="color:#a6e22e">second</span>
</span></span><span style="display:flex;"><span>   <span style="color:#a6e22e">second</span> = <span style="color:#a6e22e">diners</span>[<span style="color:#a6e22e">i</span>]
</span></span><span style="display:flex;"><span>  } <span style="color:#66d9ef">else</span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">diners</span>[<span style="color:#a6e22e">i</span>] &gt; <span style="color:#a6e22e">third</span> {
</span></span><span style="display:flex;"><span>   <span style="color:#a6e22e">third</span> = <span style="color:#a6e22e">diners</span>[<span style="color:#a6e22e">i</span>]
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> <span style="color:#75715e">// list the final result of who gets a fork
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">withForks</span> = append(<span style="color:#a6e22e">withForks</span>, <span style="color:#a6e22e">first</span>, <span style="color:#a6e22e">second</span>, <span style="color:#a6e22e">third</span>)
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">withForks</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Here&rsquo;s how the new program works:</p>
<p>Initially, diner <code>2</code> (the first in the list) is assigned the <code>first</code> fork. The other forks remain unassigned.</p>
<p>Then, diner <code>88</code> is assigned the first fork instead. Diner <code>2</code> gets the <code>second</code> one.</p>
<p>Diner <code>87</code> isn&rsquo;t greater than <code>first</code> which is currently <code>88</code>, but it is greater than <code>2</code> who has the <code>second</code> fork. So, the <code>second</code> fork goes to <code>87</code>. Diner <code>2</code> gets the <code>third</code> fork.</p>
<p>Continuing in this violent and rapid fork exchange, diner <code>16</code> is then assigned the <code>third</code> fork instead of <code>2</code>, and so on.</p>
<p>We can add a print statement in the loop to see how the fork assignments play out:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span><span style="color:#ae81ff">0</span> <span style="color:#ae81ff">0</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">2</span> <span style="color:#ae81ff">0</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">88</span> <span style="color:#ae81ff">2</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">88</span> <span style="color:#ae81ff">87</span> <span style="color:#ae81ff">2</span>
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">88</span> <span style="color:#ae81ff">87</span> <span style="color:#ae81ff">16</span>
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">88</span> <span style="color:#ae81ff">87</span> <span style="color:#ae81ff">42</span>
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">88</span> <span style="color:#ae81ff">87</span> <span style="color:#ae81ff">42</span>
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">88</span> <span style="color:#ae81ff">87</span> <span style="color:#ae81ff">42</span>
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">88</span> <span style="color:#ae81ff">87</span> <span style="color:#ae81ff">42</span>
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">88</span> <span style="color:#ae81ff">87</span> <span style="color:#ae81ff">43</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">[</span><span style="color:#ae81ff">88</span> <span style="color:#ae81ff">87</span> 56<span style="color:#f92672">]</span>
</span></span></code></pre></div><p>This program is much faster, and the whole epic struggle for fork domination is over in 47 nanoseconds.</p>
<p>As you can see, with a little change in perspective and some refactoring, we&rsquo;ve made this simple bit of code faster and more efficient.</p>
<p>Well, it looks like our fifteen minute coffee break is up! I hope I&rsquo;ve given you a comprehensive introduction to calculating time complexity. Time to get back to work, hopefully applying your new knowledge to write more effective code! Or maybe just sound smart at your next office party. :)</p>
<h2 id="references">References</h2>
<p>&ldquo;If I have seen further it is by standing on the shoulders of Giants.&rdquo; &ndash;Isaac Newton, 1675</p>
<ol>
<li>Antti Laaksonen. <em><a href="https://cses.fi/book.pdf">Competitive Programmer&rsquo;s Handbook (pdf)</a>,</em> 2017</li>
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/Big_O_notation">Big O notation</a></li>
<li>StackOverflow: <a href="https://stackoverflow.com/a/487278">What is a plain English explanation of “Big O” notation?</a></li>
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/Polynomial">Polynomial</a></li>
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/NP-completeness">NP-completeness</a></li>
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/NP-hardness">NP-hardness</a></li>
<li><a href="https://www.desmos.com/">Desmos graph calculator</a></li>
</ol>
]]></content></entry><entry><title type="html">Knapsack problem algorithms for my real-life carry-on knapsack</title><link href="https://victoria.dev/archive/knapsack-problem-algorithms-for-my-real-life-carry-on-knapsack/"/><id>https://victoria.dev/archive/knapsack-problem-algorithms-for-my-real-life-carry-on-knapsack/</id><author><name>Victoria Drake</name></author><published>2018-05-09T21:00:35-04:00</published><updated>2018-05-09T21:00:35-04:00</updated><content type="html"><![CDATA[<h2 id="the-knapsack-problem">The knapsack problem</h2>
<p>I&rsquo;m a nomad and live out of one carry-on bag. This means that the total weight of all my worldly possessions must fall under airline cabin baggage weight limits - usually 10kg. On some smaller airlines, however, this weight limit drops to 7kg. Occasionally, I have to decide not to bring something with me to adjust to the smaller weight limit.</p>
<p>As a practical exercise, deciding what to leave behind (or get rid of altogether) entails laying out all my things and choosing which ones to keep. That decision is based on the item&rsquo;s usefulness to me (its worth) and its weight.</p>
<figure><img src="/archive/knapsack-problem-algorithms-for-my-real-life-carry-on-knapsack/knapsack-stuff.jpeg"
    alt="All my stuff."><figcaption>
      <p>This is all my stuff, and my Minaal Carry-on bag.</p>
    </figcaption>
</figure>

<p>Being a programmer, I&rsquo;m aware that decisions like this could be made more efficiently by a computer. It&rsquo;s done so frequently and so ubiquitously, in fact, that many will recognize this scenario as the classic <em>packing problem</em> or <em>knapsack problem.</em> How do I go about telling a computer to put as many important items in my bag as possible while coming in at or under a weight limit of 7kg? With algorithms! Yay!</p>
<p>I&rsquo;ll discuss two common approaches to solving the knapsack problem: one called a <em>greedy algorithm,</em> and another called <em>dynamic programming</em> (a little harder, but better, faster, stronger&hellip;).</p>
<p>Let&rsquo;s get to it.</p>
<h2 id="the-set-up">The set up</h2>
<p>I prepared my data in the form of a CSV file with three columns: the item&rsquo;s name (a string), a representation of its worth (an integer), and its weight in grams (an integer). There are 40 items in total. I represented worth by ranking each item from 40 to 1, with 40 being the most important and 1 equating with something like &ldquo;why do I even have this again?&rdquo; (If you&rsquo;ve never listed out all your possessions and ranked them by order of how useful they are to you, I highly recommend you try it. It can be a very revealing exercise.)</p>
<p><strong>Total weight of all items and bag:</strong> 9003g</p>
<p><strong>Bag weight:</strong> 1415g</p>
<p><strong>Airline limit:</strong> 7000g</p>
<p><strong>Maximum weight of items I can pack:</strong> 5585g</p>
<p><strong>Total possible worth of items:</strong> 820</p>
<p><strong>The challenge:</strong> Pack as many items as the limit allows while maximizing the total worth.</p>
<h2 id="data-structures">Data structures</h2>
<h3 id="reading-in-a-file">Reading in a file</h3>
<p>Before we can begin thinking about how to solve the knapsack problem, we have to solve the problem of reading in and storing our data. Thankfully, the Go standard library&rsquo;s <code>io/ioutil</code> package makes the first part straightforward.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;io/ioutil&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">check</span>(<span style="color:#a6e22e">e</span> <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">e</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>        panic(<span style="color:#a6e22e">e</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">readItems</span>(<span style="color:#a6e22e">path</span> <span style="color:#66d9ef">string</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">dat</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">ioutil</span>.<span style="color:#a6e22e">ReadFile</span>(<span style="color:#a6e22e">path</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">check</span>(<span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Print</span>(string(<span style="color:#a6e22e">dat</span>))
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The <code>ReadFile()</code> function takes a file path and returns the file&rsquo;s contents and an error (<code>nil</code> if the call is successful) so we&rsquo;ve also created a <code>check()</code> function to handle any errors that might be returned. In a real-world application we probably would want to do something more sophisticated than <code>panic</code>, but that&rsquo;s not important right now.</p>
<h3 id="creating-a-struct">Creating a struct</h3>
<p>Now that we&rsquo;ve got our data, we should probably do something with it. Since we&rsquo;re working with real-life items and a real-life bag, let&rsquo;s create some types to represent them and make it easier to conceptualize our program. A <code>struct</code> in Go is a typed collection of fields. Here are our two types:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">item</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">name</span>          <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">worth</span>, <span style="color:#a6e22e">weight</span> <span style="color:#66d9ef">int</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">bag</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">bagWeight</span>, <span style="color:#a6e22e">currItemsWeight</span>, <span style="color:#a6e22e">maxItemsWeight</span>, <span style="color:#a6e22e">totalWeight</span> <span style="color:#66d9ef">int</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">items</span>                                                   []<span style="color:#a6e22e">item</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>It is helpful to use field names that are very descriptive. You can see that the structs are set up just as we&rsquo;ve described the things they represent. An <code>item</code> has a <code>name</code> (string), and a <code>worth</code> and <code>weight</code> (integers). A <code>bag</code> has several fields of type <code>int</code> representing its attributes, and also has the ability to hold <code>items</code>, represented in the struct as a slice of <code>item</code> type thingamabobbers.</p>
<h3 id="parsing-and-storing-our-data">Parsing and storing our data</h3>
<p>Several comprehensive Go packages exist that we could use to parse our CSV data&hellip; but where&rsquo;s the fun in that? Let&rsquo;s go basic with some string splitting and a for loop. Here&rsquo;s our updated <code>readItems()</code> function:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">readItems</span>(<span style="color:#a6e22e">path</span> <span style="color:#66d9ef">string</span>) []<span style="color:#a6e22e">item</span> {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">dat</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">ioutil</span>.<span style="color:#a6e22e">ReadFile</span>(<span style="color:#a6e22e">path</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">check</span>(<span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">lines</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">strings</span>.<span style="color:#a6e22e">Split</span>(string(<span style="color:#a6e22e">dat</span>), <span style="color:#e6db74">&#34;\n&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">itemList</span> <span style="color:#f92672">:=</span> make([]<span style="color:#a6e22e">item</span>, <span style="color:#ae81ff">0</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span>, <span style="color:#a6e22e">v</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">lines</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">continue</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">s</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">strings</span>.<span style="color:#a6e22e">Split</span>(<span style="color:#a6e22e">v</span>, <span style="color:#e6db74">&#34;,&#34;</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">newItemWorth</span>, <span style="color:#a6e22e">_</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">strconv</span>.<span style="color:#a6e22e">Atoi</span>(<span style="color:#a6e22e">s</span>[<span style="color:#ae81ff">1</span>])
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">newItemWeight</span>, <span style="color:#a6e22e">_</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">strconv</span>.<span style="color:#a6e22e">Atoi</span>(<span style="color:#a6e22e">s</span>[<span style="color:#ae81ff">2</span>])
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">newItem</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">item</span>{<span style="color:#a6e22e">name</span>: <span style="color:#a6e22e">s</span>[<span style="color:#ae81ff">0</span>], <span style="color:#a6e22e">worth</span>: <span style="color:#a6e22e">newItemWorth</span>, <span style="color:#a6e22e">weight</span>: <span style="color:#a6e22e">newItemWeight</span>}
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">itemList</span> = append(<span style="color:#a6e22e">itemList</span>, <span style="color:#a6e22e">newItem</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">itemList</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Using <code>strings.Split</code>, we split our <code>dat</code> on newlines. We then create an empty <code>itemList</code> to hold our items.</p>
<p>In our for loop, we skip the first line of our CSV file (the headers) then iterate over each line. We use <code>strconv.Atoi</code> (read &ldquo;A to i&rdquo;) to convert the values for each item&rsquo;s worth and weight into integers. We then create a <code>newItem</code> with these field values and append it to the <code>itemList</code>. Finally, we return <code>itemList</code>.</p>
<p>Here&rsquo;s what our set up looks like so far:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;io/ioutil&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;strconv&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;strings&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">item</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">name</span>          <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">worth</span>, <span style="color:#a6e22e">weight</span> <span style="color:#66d9ef">int</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">bag</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">bagWeight</span>, <span style="color:#a6e22e">currItemsWeight</span>, <span style="color:#a6e22e">maxItemsWeight</span>, <span style="color:#a6e22e">totalWeight</span>, <span style="color:#a6e22e">totalWorth</span> <span style="color:#66d9ef">int</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">items</span>                                                               []<span style="color:#a6e22e">item</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">check</span>(<span style="color:#a6e22e">e</span> <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">e</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>        panic(<span style="color:#a6e22e">e</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">readItems</span>(<span style="color:#a6e22e">path</span> <span style="color:#66d9ef">string</span>) []<span style="color:#a6e22e">item</span> {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">dat</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">ioutil</span>.<span style="color:#a6e22e">ReadFile</span>(<span style="color:#a6e22e">path</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">check</span>(<span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">lines</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">strings</span>.<span style="color:#a6e22e">Split</span>(string(<span style="color:#a6e22e">dat</span>), <span style="color:#e6db74">&#34;\n&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">itemList</span> <span style="color:#f92672">:=</span> make([]<span style="color:#a6e22e">item</span>, <span style="color:#ae81ff">0</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span>, <span style="color:#a6e22e">v</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">lines</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">continue</span> <span style="color:#75715e">// skip the headers on the first line
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">s</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">strings</span>.<span style="color:#a6e22e">Split</span>(<span style="color:#a6e22e">v</span>, <span style="color:#e6db74">&#34;,&#34;</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">newItemWorth</span>, <span style="color:#a6e22e">_</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">strconv</span>.<span style="color:#a6e22e">Atoi</span>(<span style="color:#a6e22e">s</span>[<span style="color:#ae81ff">1</span>])
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">newItemWeight</span>, <span style="color:#a6e22e">_</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">strconv</span>.<span style="color:#a6e22e">Atoi</span>(<span style="color:#a6e22e">s</span>[<span style="color:#ae81ff">2</span>])
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">newItem</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">item</span>{<span style="color:#a6e22e">name</span>: <span style="color:#a6e22e">s</span>[<span style="color:#ae81ff">0</span>], <span style="color:#a6e22e">worth</span>: <span style="color:#a6e22e">newItemWorth</span>, <span style="color:#a6e22e">weight</span>: <span style="color:#a6e22e">newItemWeight</span>}
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">itemList</span> = append(<span style="color:#a6e22e">itemList</span>, <span style="color:#a6e22e">newItem</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">itemList</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Now that we&rsquo;ve got our data structures set up, let&rsquo;s get packing (🥁) on the first approach.</p>
<h2 id="greedy-algorithm">Greedy algorithm</h2>
<p>A greedy algorithm is the most straightforward approach to solving the knapsack problem, in that it is a one-pass algorithm that constructs a single final solution. At each stage of the problem, the greedy algorithm picks the option that is locally optimal, meaning it looks like the most suitable option right now. It does not revise its previous choices as it progresses through our data set.</p>
<h3 id="building-our-greedy-algorithm">Building our greedy algorithm</h3>
<p>The steps of the algorithm we&rsquo;ll use to solve our knapsack problem are:</p>
<ol>
<li>Sort items by worth, in descending order.</li>
<li>Start with the highest worth item. Put items into the bag until the next item on the list cannot fit.</li>
<li>Try to fill any remaining capacity with the next item on the list that can fit.</li>
</ol>
<p>If you read my <a href="https://victoria.dev/verbose/build-from-scratch/">article about solving problems and making paella</a>, you&rsquo;ll know that I always start by figuring out what the next most important question is. In this case, there are three main operations we need to figure out how to do:</p>
<ul>
<li>Sort items by worth.</li>
<li>Put an item in the bag.</li>
<li>Check to see if the bag is full.</li>
</ul>
<p>The first one is just a docs lookup away. Here&rsquo;s how we sort a slice in Go:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">sort</span>.<span style="color:#a6e22e">Slice</span>(<span style="color:#a6e22e">is</span>, <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">i</span>, <span style="color:#a6e22e">j</span> <span style="color:#66d9ef">int</span>) <span style="color:#66d9ef">bool</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">is</span>[<span style="color:#a6e22e">i</span>].<span style="color:#a6e22e">worth</span> &gt; <span style="color:#a6e22e">is</span>[<span style="color:#a6e22e">j</span>].<span style="color:#a6e22e">worth</span>
</span></span><span style="display:flex;"><span>})
</span></span></code></pre></div><p>The <code>sort.Slice()</code> function orders our items according to the less function we provide. In this case, it will order the highest worth items before the lowest worth items.</p>
<p>Given that we don&rsquo;t want to put an item in the bag if it doesn&rsquo;t fit, we&rsquo;ll complete the last two tasks in reverse. First, we&rsquo;ll check to see if the item fits. If so, it goes in the bag.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">b</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">bag</span>) <span style="color:#a6e22e">addItem</span>(<span style="color:#a6e22e">i</span> <span style="color:#a6e22e">item</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">currItemsWeight</span><span style="color:#f92672">+</span><span style="color:#a6e22e">i</span>.<span style="color:#a6e22e">weight</span> <span style="color:#f92672">&lt;=</span> <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">maxItemsWeight</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">currItemsWeight</span> <span style="color:#f92672">+=</span> <span style="color:#a6e22e">i</span>.<span style="color:#a6e22e">weight</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">items</span> = append(<span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">items</span>, <span style="color:#a6e22e">i</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">New</span>(<span style="color:#e6db74">&#34;could not fit item&#34;</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Notice the <code>*</code> in our first line there. That indicates that <code>bag</code> is a pointer receiver (as opposed to a value receiver). It&rsquo;s a concept that can be slightly confusing if you&rsquo;re new to Go. Here are <a href="https://github.com/golang/go/wiki/CodeReviewComments#receiver-type">some things to consider</a> that might help you decide when to use a value receiver and when to use a pointer receiver. For the purposes of our <code>addItem()</code> function, this case applies:</p>
<blockquote>
<p>If the method needs to mutate the receiver, the receiver must be a pointer.</p>
</blockquote>
<p>Our use of a pointer receiver tells our function we want to operate on <em>this specific bag in particular</em>, not a new bag. It&rsquo;s important because without it, every item would always fit in a newly created bag! A little detail like this can make the difference between code that works and code that keeps you up until 4am chugging Red Bull and muttering to yourself. (Go to bed on time even if your code doesn&rsquo;t work - you&rsquo;ll thank me later.)</p>
<p>Now that we&rsquo;ve got our components, let&rsquo;s put together our greedy algorithm:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">greedy</span>(<span style="color:#a6e22e">is</span> []<span style="color:#a6e22e">item</span>, <span style="color:#a6e22e">b</span> <span style="color:#a6e22e">bag</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">sort</span>.<span style="color:#a6e22e">Slice</span>(<span style="color:#a6e22e">is</span>, <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">i</span>, <span style="color:#a6e22e">j</span> <span style="color:#66d9ef">int</span>) <span style="color:#66d9ef">bool</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">is</span>[<span style="color:#a6e22e">i</span>].<span style="color:#a6e22e">worth</span> &gt; <span style="color:#a6e22e">is</span>[<span style="color:#a6e22e">j</span>].<span style="color:#a6e22e">worth</span>
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">is</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">addItem</span>(<span style="color:#a6e22e">is</span>[<span style="color:#a6e22e">i</span>])
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">totalWeight</span> = <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">bagWeight</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">currItemsWeight</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">v</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">items</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">totalWorth</span> <span style="color:#f92672">+=</span> <span style="color:#a6e22e">v</span>.<span style="color:#a6e22e">worth</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Then in our <code>main()</code> function, we&rsquo;ll create our bag, read in our data, and call our greedy algorithm. Here&rsquo;s what it looks like, all set up and ready to go:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">minaal</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">bag</span>{<span style="color:#a6e22e">bagWeight</span>: <span style="color:#ae81ff">1415</span>, <span style="color:#a6e22e">currItemsWeight</span>: <span style="color:#ae81ff">0</span>, <span style="color:#a6e22e">maxItemsWeight</span>: <span style="color:#ae81ff">5585</span>}
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">itemList</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">readItems</span>(<span style="color:#e6db74">&#34;objects.csv&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">greedy</span>(<span style="color:#a6e22e">itemList</span>, <span style="color:#a6e22e">minaal</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="greedy-algorithm-results">Greedy algorithm results</h3>
<p>So how does this algorithm do when it comes to efficiently packing our bag to maximize its total worth? Here&rsquo;s the result:</p>
<p><strong>Total weight of bag and items:</strong> 6987g</p>
<p><strong>Total worth of packed items:</strong> 716</p>
<p>Here are the items our greedy algorithm chose, sorted by worth:</p>
<table>
<thead>
<tr>
<th>Item</th>
<th>Worth</th>
<th>Weight</th>
</tr>
</thead>
<tbody>
<tr>
<td>Lenovo X1 Carbon (5th Gen)</td>
<td>40</td>
<td>112</td>
</tr>
<tr>
<td>10 pairs thongs</td>
<td>39</td>
<td>80</td>
</tr>
<tr>
<td>5 Underarmour Strappy</td>
<td>38</td>
<td>305</td>
</tr>
<tr>
<td>1 pair Uniqlo leggings</td>
<td>37</td>
<td>185</td>
</tr>
<tr>
<td>2 Lululemon Cool Racerback</td>
<td>36</td>
<td>174</td>
</tr>
<tr>
<td>Chargers and cables in Mini Bomber Travel Kit</td>
<td>35</td>
<td>665</td>
</tr>
<tr>
<td>The Roost Stand</td>
<td>34</td>
<td>170</td>
</tr>
<tr>
<td>ThinkPad Compact Bluetooth Keyboard with trackpoint</td>
<td>33</td>
<td>460</td>
</tr>
<tr>
<td>Seagate Backup PlusSlim</td>
<td>32</td>
<td>159</td>
</tr>
<tr>
<td>1 pair black denim shorts</td>
<td>31</td>
<td>197</td>
</tr>
<tr>
<td>2 pairs Nike Pro shorts</td>
<td>30</td>
<td>112</td>
</tr>
<tr>
<td>2 pairs Lululemon shorts</td>
<td>29</td>
<td>184</td>
</tr>
<tr>
<td>Isabella T-Strap Croc sandals</td>
<td>28</td>
<td>200</td>
</tr>
<tr>
<td>2 Underarmour HeatGear CoolSwitch tank tops</td>
<td>27</td>
<td>138</td>
</tr>
<tr>
<td>5 pairs black socks</td>
<td>26</td>
<td>95</td>
</tr>
<tr>
<td>2 pairs Injinji Women&rsquo;s Run Lightweight No-Show Toe Socks</td>
<td>25</td>
<td>54</td>
</tr>
<tr>
<td>1 fancy tank top</td>
<td>24</td>
<td>71</td>
</tr>
<tr>
<td>1 light and stretchylong-sleeve shirt (Gap Fit)</td>
<td>23</td>
<td>147</td>
</tr>
<tr>
<td>Uniqlo Ultralight Down insulating jacket</td>
<td>22</td>
<td>235</td>
</tr>
<tr>
<td>Patagonia Torrentshell</td>
<td>21</td>
<td>301</td>
</tr>
<tr>
<td>Lightweight Merino Wool Buff</td>
<td>20</td>
<td>50</td>
</tr>
<tr>
<td>1 LBD (H&amp;M)</td>
<td>19</td>
<td>174</td>
</tr>
<tr>
<td>Field Notes Pitch Black Memo Book Dot-Graph</td>
<td>18</td>
<td>68</td>
</tr>
<tr>
<td>Innergie PocketCell USB-C 6000mAh power bank</td>
<td>17</td>
<td>14</td>
</tr>
<tr>
<td>JBL Reflect Mini Bluetooth Sport Headphones</td>
<td>13</td>
<td>14</td>
</tr>
<tr>
<td>Oakley Latch Sunglasses</td>
<td>11</td>
<td>30</td>
</tr>
<tr>
<td>Petzl E+LITE Emergency Headlamp</td>
<td>8</td>
<td>27</td>
</tr>
</tbody>
</table>
<p>It&rsquo;s clear that the greedy algorithm is a straightforward way to quickly find a feasible solution. For small data sets, it will probably be close to the optimal solution. The algorithm packed a total item worth of 716 (104 points less than the maximum possible value), while filling the bag with just 13g left over.</p>
<p>As we learned earlier, the greedy algorithm doesn&rsquo;t improve upon the solution it returns. It simply adds the next highest worth item it can to the bag.</p>
<p>Let&rsquo;s look at another method for solving the knapsack problem that will give us the optimal solution - the highest possible total worth under the weight limit.</p>
<h2 id="dynamic-programming">Dynamic programming</h2>
<p>The name &ldquo;dynamic programming&rdquo; can be a bit misleading. It&rsquo;s not a style of programming, as the name might cause you to infer, but simply another approach.</p>
<p>Dynamic programming differs from the straightforward greedy algorithm in a few key ways. Firstly, a dynamic programming bag packing solution enumerates the entire solution space with all possibilities of item combinations that could be used to pack our bag. Where a greedy algorithm chooses the most optimal <em>local</em> solution, dynamic programming algorithms are able to find the most optimal <em>global</em> solution.</p>
<p>Secondly, dynamic programming uses memoization to store the results of previously computed operations and returns the cached result when the operation occurs again. This allows it to &ldquo;remember&rdquo; previous combinations. This takes less time than it would to re-compute the answer again.</p>
<h3 id="building-our-dynamic-programming-algorithm">Building our dynamic programming algorithm</h3>
<p>To use dynamic programming to find the optimal recipe for packing our bag, we&rsquo;ll need to:</p>
<ol>
<li>Create a matrix representing all subsets of the items (the solution space) with rows representing items and columns representing the bag&rsquo;s remaining weight capacity</li>
<li>Loop through the matrix and calculate the worth that can be obtained by each combination of items at each stage of the bag&rsquo;s capacity</li>
<li>Examine the completed matrix to determine which items to add to the bag in order to produce the maximum possible worth for the bag in total</li>
</ol>
<p>It will be most helpful to visualize our solution space. Here&rsquo;s a representation of what we&rsquo;re building with our code:</p>
<figure><img src="/archive/knapsack-problem-algorithms-for-my-real-life-carry-on-knapsack/knapsack-matrix.jpg"
    alt="A sketch of the matrix with rows for items and columns for grams of weight."><figcaption>
      <p>The empty knapsackian multiverse.</p>
    </figcaption>
</figure>

<p>In Go, we can create this matrix as a slice of slices.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">matrix</span> <span style="color:#f92672">:=</span> make([][]<span style="color:#66d9ef">int</span>, <span style="color:#a6e22e">numItems</span><span style="color:#f92672">+</span><span style="color:#ae81ff">1</span>) <span style="color:#75715e">// rows representing items
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">matrix</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">matrix</span>[<span style="color:#a6e22e">i</span>] = make([]<span style="color:#66d9ef">int</span>, <span style="color:#a6e22e">capacity</span><span style="color:#f92672">+</span><span style="color:#ae81ff">1</span>) <span style="color:#75715e">// columns representing grams of weight
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>}
</span></span></code></pre></div><p>We&rsquo;ve padded the rows and columns by <code>1</code> so that the indicies match the item and weight numbers.</p>
<p>Now that we&rsquo;ve created our matrix, we&rsquo;ll fill it by looping over the rows and the columns:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// loop through table rows
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">1</span>; <span style="color:#a6e22e">i</span> <span style="color:#f92672">&lt;=</span> <span style="color:#a6e22e">numItems</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// loop through table columns
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">w</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">1</span>; <span style="color:#a6e22e">w</span> <span style="color:#f92672">&lt;=</span> <span style="color:#a6e22e">capacity</span>; <span style="color:#a6e22e">w</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// do stuff in each element
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Then for each element, we&rsquo;ll calculate the worth value to ascribe to it. We do this with code that represents the following:</p>
<blockquote>
<p>If the item at the index matching the current row fits within the weight capacity represented by the current column, take the maximum of either:</p>
<ol>
<li>The total worth of the items already in the bag or,</li>
<li>The total worth of all the items in the bag except the item at the previous row index, plus the new item&rsquo;s worth</li>
</ol>
</blockquote>
<p>In other words, as our algorithm considers one of the items, we&rsquo;re asking it to decide whether this item added to the bag would produce a higher total worth than the last item it added to the bag, at the bag&rsquo;s current total weight. If this current item is a better choice, put it in - if not, leave it out.</p>
<p>Here&rsquo;s the code that accomplishes this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// if weight of item matching this index can fit at the current capacity column...
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">if</span> <span style="color:#a6e22e">is</span>[<span style="color:#a6e22e">i</span><span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>].<span style="color:#a6e22e">weight</span> <span style="color:#f92672">&lt;=</span> <span style="color:#a6e22e">w</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// worth of this subset without this item
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">valueOne</span> <span style="color:#f92672">:=</span> float64(<span style="color:#a6e22e">matrix</span>[<span style="color:#a6e22e">i</span><span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>][<span style="color:#a6e22e">w</span>])
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// worth of this subset without the previous item, and this item instead
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">valueTwo</span> <span style="color:#f92672">:=</span> float64(<span style="color:#a6e22e">is</span>[<span style="color:#a6e22e">i</span><span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>].<span style="color:#a6e22e">worth</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">matrix</span>[<span style="color:#a6e22e">i</span><span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>][<span style="color:#a6e22e">w</span><span style="color:#f92672">-</span><span style="color:#a6e22e">is</span>[<span style="color:#a6e22e">i</span><span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>].<span style="color:#a6e22e">weight</span>])
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// take maximum of either valueOne or valueTwo
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">matrix</span>[<span style="color:#a6e22e">i</span>][<span style="color:#a6e22e">w</span>] = int(<span style="color:#a6e22e">math</span>.<span style="color:#a6e22e">Max</span>(<span style="color:#a6e22e">valueOne</span>, <span style="color:#a6e22e">valueTwo</span>))
</span></span><span style="display:flex;"><span><span style="color:#75715e">// if the new worth is not more, carry over the previous worth
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>} <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">matrix</span>[<span style="color:#a6e22e">i</span>][<span style="color:#a6e22e">w</span>] = <span style="color:#a6e22e">matrix</span>[<span style="color:#a6e22e">i</span><span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>][<span style="color:#a6e22e">w</span>]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>This process of comparing item combinations will continue until every item has been considered at every possible stage of the bag&rsquo;s increasing total weight. When all the above have been considered, we&rsquo;ll have enumerated the solution space - filled the matrix - with all possible total worth values.</p>
<p>We&rsquo;ll have a big chart of numbers, and in the last column at the last row we&rsquo;ll have our highest possible value.</p>
<figure><img src="/archive/knapsack-problem-algorithms-for-my-real-life-carry-on-knapsack/knapsack-matrix-filled.jpg"
    alt="A strictly representative representation of the filled matrix."><figcaption>
      <p>A strictly representative representation of the filled matrix.</p>
    </figcaption>
</figure>

<p>That&rsquo;s great, but how do we find out which combination of items were put in the bag to achieve that worth?</p>
<h3 id="getting-our-optimized-item-list">Getting our optimized item list</h3>
<p>To see which items combine to create our optimal packing list, we&rsquo;ll need to examine our matrix in reverse to the way we created it. Since we know the highest possible value is in the last row in the last column, we&rsquo;ll start there. To find the items, we:</p>
<ol>
<li>Get the value of the current cell</li>
<li>Compare the value of the current cell to the value in the cell directly above it</li>
<li>If the values differ, there was a change to the bag items; find the next cell to examine by moving backwards through the columns according to the current item&rsquo;s weight (find the value of the bag before this current item was added)</li>
<li>If the values match, there was no change to the bag items; move up to the cell in the row above and repeat</li>
</ol>
<p>The nature of the action we&rsquo;re trying to achieve lends itself well to a recursive function. If you recall from <a href="https://victoria.dev/verbose/reduce-recursion-with-pie/">my previous article about making apple pie</a>, recursive functions are simply functions that call themselves under certain conditions. Here&rsquo;s what it looks like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">checkItem</span>(<span style="color:#a6e22e">b</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">bag</span>, <span style="color:#a6e22e">i</span> <span style="color:#66d9ef">int</span>, <span style="color:#a6e22e">w</span> <span style="color:#66d9ef">int</span>, <span style="color:#a6e22e">is</span> []<span style="color:#a6e22e">item</span>, <span style="color:#a6e22e">matrix</span> [][]<span style="color:#66d9ef">int</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">&lt;=</span> <span style="color:#ae81ff">0</span> <span style="color:#f92672">||</span> <span style="color:#a6e22e">w</span> <span style="color:#f92672">&lt;=</span> <span style="color:#ae81ff">0</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>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">pick</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">matrix</span>[<span style="color:#a6e22e">i</span>][<span style="color:#a6e22e">w</span>]
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">pick</span> <span style="color:#f92672">!=</span> <span style="color:#a6e22e">matrix</span>[<span style="color:#a6e22e">i</span><span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>][<span style="color:#a6e22e">w</span>] {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">addItem</span>(<span style="color:#a6e22e">is</span>[<span style="color:#a6e22e">i</span><span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>])
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">checkItem</span>(<span style="color:#a6e22e">b</span>, <span style="color:#a6e22e">i</span><span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>, <span style="color:#a6e22e">w</span><span style="color:#f92672">-</span><span style="color:#a6e22e">is</span>[<span style="color:#a6e22e">i</span><span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>].<span style="color:#a6e22e">weight</span>, <span style="color:#a6e22e">is</span>, <span style="color:#a6e22e">matrix</span>)
</span></span><span style="display:flex;"><span>    } <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">checkItem</span>(<span style="color:#a6e22e">b</span>, <span style="color:#a6e22e">i</span><span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>, <span style="color:#a6e22e">w</span>, <span style="color:#a6e22e">is</span>, <span style="color:#a6e22e">matrix</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Our <code>checkItem()</code> function calls itself if the condition we described in step 4 is true. If step 3 is true, it also calls itself, but with different arguments.</p>
<p>Recursive functions require a base case. In this example, we want the function to stop once we run out of values of worth to compare. Thus our base case is when either <code>i</code> or <code>w</code> are <code>0</code>.</p>
<p>Here&rsquo;s how the dynamic programming approach looks when it&rsquo;s all put together:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">checkItem</span>(<span style="color:#a6e22e">b</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">bag</span>, <span style="color:#a6e22e">i</span> <span style="color:#66d9ef">int</span>, <span style="color:#a6e22e">w</span> <span style="color:#66d9ef">int</span>, <span style="color:#a6e22e">is</span> []<span style="color:#a6e22e">item</span>, <span style="color:#a6e22e">matrix</span> [][]<span style="color:#66d9ef">int</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">&lt;=</span> <span style="color:#ae81ff">0</span> <span style="color:#f92672">||</span> <span style="color:#a6e22e">w</span> <span style="color:#f92672">&lt;=</span> <span style="color:#ae81ff">0</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>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">pick</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">matrix</span>[<span style="color:#a6e22e">i</span>][<span style="color:#a6e22e">w</span>]
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">pick</span> <span style="color:#f92672">!=</span> <span style="color:#a6e22e">matrix</span>[<span style="color:#a6e22e">i</span><span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>][<span style="color:#a6e22e">w</span>] {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">addItem</span>(<span style="color:#a6e22e">is</span>[<span style="color:#a6e22e">i</span><span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>])
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">checkItem</span>(<span style="color:#a6e22e">b</span>, <span style="color:#a6e22e">i</span><span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>, <span style="color:#a6e22e">w</span><span style="color:#f92672">-</span><span style="color:#a6e22e">is</span>[<span style="color:#a6e22e">i</span><span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>].<span style="color:#a6e22e">weight</span>, <span style="color:#a6e22e">is</span>, <span style="color:#a6e22e">matrix</span>)
</span></span><span style="display:flex;"><span>    } <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">checkItem</span>(<span style="color:#a6e22e">b</span>, <span style="color:#a6e22e">i</span><span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>, <span style="color:#a6e22e">w</span>, <span style="color:#a6e22e">is</span>, <span style="color:#a6e22e">matrix</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">dynamic</span>(<span style="color:#a6e22e">is</span> []<span style="color:#a6e22e">item</span>, <span style="color:#a6e22e">b</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">bag</span>) <span style="color:#f92672">*</span><span style="color:#a6e22e">bag</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">numItems</span> <span style="color:#f92672">:=</span> len(<span style="color:#a6e22e">is</span>)          <span style="color:#75715e">// number of items in knapsack
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">capacity</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">maxItemsWeight</span> <span style="color:#75715e">// capacity of knapsack
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// create the empty matrix
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">matrix</span> <span style="color:#f92672">:=</span> make([][]<span style="color:#66d9ef">int</span>, <span style="color:#a6e22e">numItems</span><span style="color:#f92672">+</span><span style="color:#ae81ff">1</span>) <span style="color:#75715e">// rows representing items
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">matrix</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">matrix</span>[<span style="color:#a6e22e">i</span>] = make([]<span style="color:#66d9ef">int</span>, <span style="color:#a6e22e">capacity</span><span style="color:#f92672">+</span><span style="color:#ae81ff">1</span>) <span style="color:#75715e">// columns representing grams of weight
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// loop through table rows
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">1</span>; <span style="color:#a6e22e">i</span> <span style="color:#f92672">&lt;=</span> <span style="color:#a6e22e">numItems</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// loop through table columns
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">w</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">1</span>; <span style="color:#a6e22e">w</span> <span style="color:#f92672">&lt;=</span> <span style="color:#a6e22e">capacity</span>; <span style="color:#a6e22e">w</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">// if weight of item matching this index can fit at the current capacity column...
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>            <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">is</span>[<span style="color:#a6e22e">i</span><span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>].<span style="color:#a6e22e">weight</span> <span style="color:#f92672">&lt;=</span> <span style="color:#a6e22e">w</span> {
</span></span><span style="display:flex;"><span>                <span style="color:#75715e">// worth of this subset without this item
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>                <span style="color:#a6e22e">valueOne</span> <span style="color:#f92672">:=</span> float64(<span style="color:#a6e22e">matrix</span>[<span style="color:#a6e22e">i</span><span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>][<span style="color:#a6e22e">w</span>])
</span></span><span style="display:flex;"><span>                <span style="color:#75715e">// worth of this subset without the previous item, and this item instead
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>                <span style="color:#a6e22e">valueTwo</span> <span style="color:#f92672">:=</span> float64(<span style="color:#a6e22e">is</span>[<span style="color:#a6e22e">i</span><span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>].<span style="color:#a6e22e">worth</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">matrix</span>[<span style="color:#a6e22e">i</span><span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>][<span style="color:#a6e22e">w</span><span style="color:#f92672">-</span><span style="color:#a6e22e">is</span>[<span style="color:#a6e22e">i</span><span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>].<span style="color:#a6e22e">weight</span>])
</span></span><span style="display:flex;"><span>                <span style="color:#75715e">// take maximum of either valueOne or valueTwo
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>                <span style="color:#a6e22e">matrix</span>[<span style="color:#a6e22e">i</span>][<span style="color:#a6e22e">w</span>] = int(<span style="color:#a6e22e">math</span>.<span style="color:#a6e22e">Max</span>(<span style="color:#a6e22e">valueOne</span>, <span style="color:#a6e22e">valueTwo</span>))
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">// if the new worth is not more, carry over the previous worth
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>            } <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">matrix</span>[<span style="color:#a6e22e">i</span>][<span style="color:#a6e22e">w</span>] = <span style="color:#a6e22e">matrix</span>[<span style="color:#a6e22e">i</span><span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>][<span style="color:#a6e22e">w</span>]
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">checkItem</span>(<span style="color:#a6e22e">b</span>, <span style="color:#a6e22e">numItems</span>, <span style="color:#a6e22e">capacity</span>, <span style="color:#a6e22e">is</span>, <span style="color:#a6e22e">matrix</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// add other statistics to the bag
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">totalWorth</span> = <span style="color:#a6e22e">matrix</span>[<span style="color:#a6e22e">numItems</span>][<span style="color:#a6e22e">capacity</span>]
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">totalWeight</span> = <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">bagWeight</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">currItemsWeight</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">b</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="dynamic-programming-results">Dynamic programming results</h3>
<p>We expect that the dynamic programming approach will give us a more optimized solution than the greedy algorithm. So did it? Here are the results:</p>
<p><strong>Total weight of bag and items:</strong> 6982g</p>
<p><strong>Total worth of packed items:</strong> 757</p>
<p>Here are the items our dynamic programming algorithm chose, sorted by worth:</p>
<table>
<thead>
<tr>
<th>Item</th>
<th>Worth</th>
<th>Weight</th>
</tr>
</thead>
<tbody>
<tr>
<td>10 pairs thongs</td>
<td>39</td>
<td>80</td>
</tr>
<tr>
<td>5 Underarmour Strappy</td>
<td>38</td>
<td>305</td>
</tr>
<tr>
<td>1 pair Uniqlo leggings</td>
<td>37</td>
<td>185</td>
</tr>
<tr>
<td>2 Lululemon Cool Racerback</td>
<td>36</td>
<td>174</td>
</tr>
<tr>
<td>Chargers and cables in Mini Bomber Travel Kit</td>
<td>35</td>
<td>665</td>
</tr>
<tr>
<td>The Roost Stand</td>
<td>34</td>
<td>170</td>
</tr>
<tr>
<td>ThinkPad Compact Bluetooth Keyboard with trackpoint</td>
<td>33</td>
<td>460</td>
</tr>
<tr>
<td>Seagate Backup Plus Slim</td>
<td>32</td>
<td>159</td>
</tr>
<tr>
<td>1 pair black denim shorts</td>
<td>31</td>
<td>197</td>
</tr>
<tr>
<td>2 pairs Nike Pro shorts</td>
<td>30</td>
<td>112</td>
</tr>
<tr>
<td>2 pairs Lululemon shorts</td>
<td>29</td>
<td>184</td>
</tr>
<tr>
<td>Isabella T-Strap Croc sandals</td>
<td>28</td>
<td>200</td>
</tr>
<tr>
<td>2 Underarmour HeatGear CoolSwitch tank tops</td>
<td>27</td>
<td>138</td>
</tr>
<tr>
<td>5 pairs black socks</td>
<td>26</td>
<td>95</td>
</tr>
<tr>
<td>2 pairs Injinji Women&rsquo;s Run Lightweight No-Show Toe Socks</td>
<td>25</td>
<td>54</td>
</tr>
<tr>
<td>1 fancy tank top</td>
<td>24</td>
<td>71</td>
</tr>
<tr>
<td>1 light and stretchy long-sleeve shirt (Gap Fit)</td>
<td>23</td>
<td>147</td>
</tr>
<tr>
<td>Uniqlo Ultralight Down insulating jacket</td>
<td>22</td>
<td>235</td>
</tr>
<tr>
<td>Patagonia Torrentshell</td>
<td>21</td>
<td>301</td>
</tr>
<tr>
<td>Lightweight Merino Wool Buff</td>
<td>20</td>
<td>50</td>
</tr>
<tr>
<td>1 LBD (H&amp;M)</td>
<td>19</td>
<td>174</td>
</tr>
<tr>
<td>Field Notes Pitch Black Memo Book Dot-Graph</td>
<td>18</td>
<td>68</td>
</tr>
<tr>
<td>Innergie PocketCell USB-C 6000mAh power bank</td>
<td>17</td>
<td>148</td>
</tr>
<tr>
<td>Important papers</td>
<td>16</td>
<td>228</td>
</tr>
<tr>
<td>Deuter First Aid Kit Active</td>
<td>15</td>
<td>144</td>
</tr>
<tr>
<td>Stanley Classic Vacuum Camp Mug 16oz</td>
<td>14</td>
<td>454</td>
</tr>
<tr>
<td>JBL Reflect Mini Bluetooth Sport Headphones</td>
<td>13</td>
<td>14</td>
</tr>
<tr>
<td>Anker SoundCore nano Bluetooth Speaker</td>
<td>12</td>
<td>80</td>
</tr>
<tr>
<td>Oakley Latch Sunglasses</td>
<td>11</td>
<td>30</td>
</tr>
<tr>
<td>Ray Ban Wayfarer Classic</td>
<td>10</td>
<td>45</td>
</tr>
<tr>
<td>Petzl E+LITE Emergency Headlamp</td>
<td>8</td>
<td>27</td>
</tr>
<tr>
<td>Peak Design Cuff Camera Wrist Strap</td>
<td>6</td>
<td>26</td>
</tr>
<tr>
<td>Travelon Micro Scale</td>
<td>5</td>
<td>125</td>
</tr>
<tr>
<td>Humangear GoBites Duo</td>
<td>3</td>
<td>22</td>
</tr>
</tbody>
</table>
<p>There&rsquo;s an obvious improvement to our dynamic programming solution over what the greedy algorithm gave us. Our total worth of 757 is 41 points greater than the greedy algorithm&rsquo;s solution of 716, and for a few grams less weight too!</p>
<h3 id="input-sort-order">Input sort order</h3>
<p>While testing my dynamic programming solution, I implemented the <a href="https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle">Fisher-Yates shuffle algorithm</a> on the input before passing it into my function, just to ensure that the answer wasn&rsquo;t somehow dependent on the sort order of the input. Here&rsquo;s what the shuffle looks like in Go:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">rand</span>.<span style="color:#a6e22e">Seed</span>(<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Now</span>().<span style="color:#a6e22e">UnixNano</span>())
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">itemList</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">j</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">rand</span>.<span style="color:#a6e22e">Intn</span>(<span style="color:#a6e22e">i</span> <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">itemList</span>[<span style="color:#a6e22e">i</span>], <span style="color:#a6e22e">itemList</span>[<span style="color:#a6e22e">j</span>] = <span style="color:#a6e22e">itemList</span>[<span style="color:#a6e22e">j</span>], <span style="color:#a6e22e">itemList</span>[<span style="color:#a6e22e">i</span>]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Of course I then realized that Go 1.10 now has a built-in shuffle&hellip; it works precisely the same way and looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">rand</span>.<span style="color:#a6e22e">Shuffle</span>(len(<span style="color:#a6e22e">itemList</span>), <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">i</span>, <span style="color:#a6e22e">j</span> <span style="color:#66d9ef">int</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">itemList</span>[<span style="color:#a6e22e">i</span>], <span style="color:#a6e22e">itemList</span>[<span style="color:#a6e22e">j</span>] = <span style="color:#a6e22e">itemList</span>[<span style="color:#a6e22e">j</span>], <span style="color:#a6e22e">itemList</span>[<span style="color:#a6e22e">i</span>]
</span></span><span style="display:flex;"><span>})
</span></span></code></pre></div><p>So did the order in which the items were processed affect the outcome? Well&hellip;</p>
<h4 id="suddenly-a-rogue-weight-appears">Suddenly&hellip; a rogue weight appears!</h4>
<p>As it turns out, in a way, the answer did depend on the order of the input. When I ran my dynamic programming algorithm several times, I sometimes saw a different total weight for the bag, though the total worth remained at 757. I initially thought this was a bug before examining the two sets of items that accompanied the two different total weight values. Everything was the same except for a few changes that collectively added up to a different item subset accounting for 14 of the 757 worth points.</p>
<p>In this case, there were two equally optimal solutions based only on the success metric of the highest total possible worth. Shuffling the input seemed to affect the placement of the items in the matrix and thus, the path that the <code>checkItem()</code> function took as it went through the matrix to find the chosen items. Since the success metric of having the highest possible worth was the same in both item sets, we don&rsquo;t have a single unique solution - there&rsquo;s two!</p>
<p>As an academic exercise, both these sets of items are correct answers. We may choose to optimize further by another metric, say, the total weight of all the items. The highest possible worth at the least possible weight could be seen as an ideal solution.</p>
<p>Here&rsquo;s the second, lighter, dynamic programming result:</p>
<p><strong>Total weight of bag and items:</strong> 6955g</p>
<p><strong>Total worth of packed items:</strong> 757</p>
<table>
<thead>
<tr>
<th>Item</th>
<th>Worth</th>
<th>Weight</th>
</tr>
</thead>
<tbody>
<tr>
<td>10 pairs thongs</td>
<td>39</td>
<td>80</td>
</tr>
<tr>
<td>5 Underarmour Strappy</td>
<td>38</td>
<td>305</td>
</tr>
<tr>
<td>1 pair Uniqlo leggings</td>
<td>37</td>
<td>185</td>
</tr>
<tr>
<td>2 Lululemon Cool Racerback</td>
<td>36</td>
<td>174</td>
</tr>
<tr>
<td>Chargers and cables in Mini Bomber Travel Kit</td>
<td>35</td>
<td>665</td>
</tr>
<tr>
<td>The Roost Stand</td>
<td>34</td>
<td>170</td>
</tr>
<tr>
<td>ThinkPad Compact Bluetooth Keyboard with trackpoint</td>
<td>33</td>
<td>460</td>
</tr>
<tr>
<td>Seagate Backup Plus Slim</td>
<td>32</td>
<td>159</td>
</tr>
<tr>
<td>1 pair black denim shorts</td>
<td>31</td>
<td>197</td>
</tr>
<tr>
<td>2 pairs Nike Pro shorts</td>
<td>30</td>
<td>112</td>
</tr>
<tr>
<td>2 pairs Lululemon shorts</td>
<td>29</td>
<td>184</td>
</tr>
<tr>
<td>Isabella T-Strap Croc sandals</td>
<td>28</td>
<td>200</td>
</tr>
<tr>
<td>2 Underarmour HeatGear CoolSwitch tank tops</td>
<td>27</td>
<td>138</td>
</tr>
<tr>
<td>5 pairs black socks</td>
<td>26</td>
<td>95</td>
</tr>
<tr>
<td>2 pairs Injinji Women&rsquo;s Run Lightweight No-Show Toe Socks</td>
<td>25</td>
<td>54</td>
</tr>
<tr>
<td>1 fancy tank top</td>
<td>24</td>
<td>71</td>
</tr>
<tr>
<td>1 light and stretchy long-sleeve shirt (Gap Fit)</td>
<td>23</td>
<td>147</td>
</tr>
<tr>
<td>Uniqlo Ultralight Down insulating jacket</td>
<td>22</td>
<td>235</td>
</tr>
<tr>
<td>Patagonia Torrentshell</td>
<td>21</td>
<td>301</td>
</tr>
<tr>
<td>Lightweight Merino Wool Buff</td>
<td>20</td>
<td>50</td>
</tr>
<tr>
<td>1 LBD (H&amp;M)</td>
<td>19</td>
<td>174</td>
</tr>
<tr>
<td>Field Notes Pitch Black Memo Book Dot-Graph</td>
<td>18</td>
<td>68</td>
</tr>
<tr>
<td>Innergie PocketCell USB-C 6000mAh power bank</td>
<td>17</td>
<td>148</td>
</tr>
<tr>
<td>Important papers</td>
<td>16</td>
<td>228</td>
</tr>
<tr>
<td>Deuter First Aid Kit Active</td>
<td>15</td>
<td>144</td>
</tr>
<tr>
<td>JBL Reflect Mini Bluetooth Sport Headphones</td>
<td>13</td>
<td>14</td>
</tr>
<tr>
<td>Anker SoundCore nano Bluetooth Speaker</td>
<td>12</td>
<td>80</td>
</tr>
<tr>
<td>Oakley Latch Sunglasses</td>
<td>11</td>
<td>30</td>
</tr>
<tr>
<td>Ray Ban Wayfarer Classic</td>
<td>10</td>
<td>45</td>
</tr>
<tr>
<td>Zip bag of toiletries</td>
<td>9</td>
<td>236</td>
</tr>
<tr>
<td>Petzl E+LITE Emergency Headlamp</td>
<td>8</td>
<td>27</td>
</tr>
<tr>
<td>Peak Design Cuff Camera Wrist Strap</td>
<td>6</td>
<td>26</td>
</tr>
<tr>
<td>Travelon Micro Scale</td>
<td>5</td>
<td>125</td>
</tr>
<tr>
<td>BlitzWolf Bluetooth Tripod/Monopod</td>
<td>4</td>
<td>150</td>
</tr>
<tr>
<td>Humangear GoBites Duo</td>
<td>3</td>
<td>22</td>
</tr>
<tr>
<td>Vapur Bottle 1L</td>
<td>1</td>
<td>41</td>
</tr>
</tbody>
</table>
<h2 id="which-approach-is-better">Which approach is better?</h2>
<h3 id="go-benchmarking">Go benchmarking</h3>
<p>The Go standard library&rsquo;s <code>testing</code> package makes it straightforward for us to <a href="https://golang.org/pkg/testing/#hdr-Benchmarks">benchmark</a> these two approaches. We can find out how long it takes each algorithm to run, and how much memory each uses. Here&rsquo;s a simple <code>main_test.go</code> 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;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;testing&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">Benchmark_greedy</span>(<span style="color:#a6e22e">b</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">B</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">itemList</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">readItems</span>(<span style="color:#e6db74">&#34;objects.csv&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span> &lt; <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">N</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">minaal</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">bag</span>{<span style="color:#a6e22e">bagWeight</span>: <span style="color:#ae81ff">1415</span>, <span style="color:#a6e22e">currItemsWeight</span>: <span style="color:#ae81ff">0</span>, <span style="color:#a6e22e">maxItemsWeight</span>: <span style="color:#ae81ff">5585</span>}
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">greedy</span>(<span style="color:#a6e22e">itemList</span>, <span style="color:#a6e22e">minaal</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">Benchmark_dynamic</span>(<span style="color:#a6e22e">b</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">B</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">itemList</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">readItems</span>(<span style="color:#e6db74">&#34;objects.csv&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span> &lt; <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">N</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">minaal</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">bag</span>{<span style="color:#a6e22e">bagWeight</span>: <span style="color:#ae81ff">1415</span>, <span style="color:#a6e22e">currItemsWeight</span>: <span style="color:#ae81ff">0</span>, <span style="color:#a6e22e">maxItemsWeight</span>: <span style="color:#ae81ff">5585</span>}
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">dynamic</span>(<span style="color:#a6e22e">itemList</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">minaal</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>We can run <code>go test -bench=. -benchmem</code> to see these results:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>Benchmark_greedy-4       <span style="color:#ae81ff">1000000</span>              <span style="color:#ae81ff">1619</span> ns/op            <span style="color:#ae81ff">2128</span> B/op          <span style="color:#ae81ff">9</span> allocs/op
</span></span><span style="display:flex;"><span>Benchmark_dynamic-4         <span style="color:#ae81ff">1000</span>           <span style="color:#ae81ff">1545322</span> ns/op         <span style="color:#ae81ff">2020332</span> B/op         <span style="color:#ae81ff">49</span> allocs/op
</span></span></code></pre></div><h4 id="greedy-algorithm-performance">Greedy algorithm performance</h4>
<p>After running the greedy algorithm 1,000,000 times, the speed of the algorithm was reliably measured to be 0.001619 milliseconds (translation: very fast). It required 2128 Bytes or 2-ish kilobytes of memory and 9 distinct memory allocations per iteration.</p>
<h4 id="dynamic-programming-performance">Dynamic programming performance</h4>
<p>The dynamic programming algorithm was run 1,000 times. Its speed was measured to be 1.545322 milliseconds or 0.001545322 seconds (translation: still pretty fast). It required 2,020,332 Bytes or 2-ish Megabytes, and 49 distinct memory allocations per iteration.</p>
<h3 id="the-verdict">The verdict</h3>
<p>Part of choosing the right approach to solving any programming problem is taking into account the size of the input data set. In this case, it&rsquo;s a small one. In this scenario, a one-pass greedy algorithm will always be faster and less resource-needy than dynamic programming, simply because it has fewer steps. Our greedy algorithm was almost two orders of magnitude faster and less memory-hungry than our dynamic programming algorithm.</p>
<p>Not having those extra steps, however, means that getting the best possible solution from the greedy algorithm is unlikely.</p>
<p>It&rsquo;s clear that the dynamic programming algorithm gave us better numbers: a lower weight, and higher overall worth.</p>
<table>
<thead>
<tr>
<th></th>
<th>Greedy algorithm</th>
<th>Dynamic programming</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Total weight:</strong></td>
<td>6987g</td>
<td>6955g</td>
</tr>
<tr>
<td><strong>Total worth:</strong></td>
<td>716</td>
<td>757</td>
</tr>
</tbody>
</table>
<p>Where dynamic programming on small data sets lacks in performance, it makes up in optimization. The question then becomes whether that additional optimization is worth the performance cost.</p>
<p>&ldquo;Better,&rdquo; of course, is a subjective judgement. If speed and low resource usage is our success metric, then the greedy algorithm is clearly better. If the total worth of items in the bag is our success metric, then dynamic programming is clearly better. However, our scenario is a practical one, and only one of these algorithm designs returned an answer I&rsquo;d choose. In optimizing for the overall greatest possible total worth of the items in the bag, the dynamic programming algorithm left out my highest-worth, but also heaviest, item: my laptop. The chargers and cables, Roost stand, and keyboard that were included aren&rsquo;t much use without it.</p>
<h3 id="better-algorithm-design">Better algorithm design</h3>
<p>There&rsquo;s a simple way to alter the dynamic programming approach so that the laptop is always included: we can modify the data so that the worth of the laptop is greater than the sum of the worth of all the other items. (Try it out!)</p>
<p>Perhaps in re-designing the dynamic programming algorithm to be more practical, we might choose another success metric that better reflects an item&rsquo;s importance, instead of a subjective worth value. There are many possible metrics we can use to represent the value of an item. Here are a few examples of a good proxy:</p>
<ul>
<li>Amount of time spent using the item</li>
<li>Initial cost of purchasing the item</li>
<li>Cost of replacement if the item were lost today</li>
<li>Dollar value of the product of using the item</li>
</ul>
<p>By the same token, the greedy algorithm&rsquo;s results might be improved with the use of one of these alternate metrics.</p>
<p>On top of choosing an appropriate approach to solving the knapsack problem in general, it is helpful to design our algorithm in a way that translates the practicalities of a scenario into code.</p>
<p>There are many considerations for better algorithm design beyond the scope of this introductory post. One of these is <strong>time complexity</strong>, and I&rsquo;ve <a href="/verbose/introduction-time-complexity/">written about it here</a>. A future algorithm may very well decide my bag&rsquo;s contents on the next trip, but we&rsquo;re not quite there yet. Stay tuned!</p>
]]></content></entry><entry><title type="html">Why I&amp;#39;m automatically deleting my old tweets using AWS Lambda</title><link href="https://victoria.dev/archive/why-im-automatically-deleting-my-old-tweets-using-aws-lambda/"/><id>https://victoria.dev/archive/why-im-automatically-deleting-my-old-tweets-using-aws-lambda/</id><author><name>Victoria Drake</name></author><published>2018-04-12T06:37:48-06:00</published><updated>2018-04-12T06:37:48-06:00</updated><content type="html"><![CDATA[<p>From now on, my tweets are ephemeral. Here&rsquo;s why I&rsquo;m deleting all my old tweets and the AWS Lambda function that does it for free.</p>
<h1 id="stuff-and-opinions">Stuff and opinions</h1>
<p>I&rsquo;ve only been a one-bag nomad for a little over a year and a half. Before that, I lived as most people do in an apartment or a house. I owned furniture, more clothing than I strictly needed, and enough &ldquo;stuff&rdquo; to fill at least a few moving boxes. If I went to live somewhere else, moving for school or family or work, I packed up all my things and brought them with me. Over the years, I accumulated more and more stuff.</p>
<p>Adopting what many would call a minimalist lifestyle has rapidly changed a lot of my longstanding views. Giving away all my stuff (an idea I once thought to be interesting in principle but practically a little bit ridiculous) has become normal. It&rsquo;s normal for me, now, to not own things that I don&rsquo;t use on a regular basis. I don&rsquo;t keep wall shelves packed with old books or dishes or clothing or childhood toys because those items aren&rsquo;t relevant to me anymore. I just keep fond memories, instead.</p>
<p>Imagine, for a moment, that I still lived in a house. Imagine that in that house, on the fridge, is a drawing I made when I was six-years-old. In the bottom right corner of that drawing scribbled in green crayon are the words &ldquo;broccoli is dumb - Victoria, Age 6.&rdquo;</p>
<p>If you were in my house and saw that drawing on the fridge, would you assume that the statement &ldquo;broccoli is dumb&rdquo; comprised an accurate and current account of my opinions on broccoli? Of course not. I was six when I wrote that. I&rsquo;ve had plenty of time to change my mind.</p>
<h1 id="social-media-isnt-social">Social media isn&rsquo;t social</h1>
<p>I have a friend whom I&rsquo;ve known since we were both in kindergarten. We went through grade school together, then spoke to and saw each other on infrequent occasions across the years. We&rsquo;re both adults now. Sometimes when we chat, we&rsquo;ll recall some amusing memory from when we were younger. The nature of memory being what it is, I have no illusion that what we recall is recounted with much accuracy. Our impressions of things that happened - mistakes we made and moments of victory alike - are coloured by the experiences we&rsquo;ve had since then, and all the things we&rsquo;ve learned. An awkward moment at a school colleague&rsquo;s birthday party becomes an example of a child learning to socialize, instead of the world-ending moment of embarrassment it probably felt like at the time.</p>
<p>This is how memory works. In a sense, it gets updated, as well it should. People living in small communities remember things that their neighbour did many years ago, but recall them in the context of who their neighbour is now, and what their current relationship is like. This re-colouring of history is an important part of how people <a href="https://www.smithsonianmag.com/science-nature/how-our-brains-make-memories-14466850/">heal</a>, <a href="http://news.feinberg.northwestern.edu/2014/02/memory_rewrite/">make good decisions</a>, and <a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3709095/">socialize</a>.</p>
<p>Social media does not do this. Your perfectly preserved tweet from five days or five years ago can be recalled with absolute accuracy. For most people, this is not particularly worrying. We tend to tweet about pretty mundane things - things that pop into mind when we&rsquo;re bored and want someone to notice us. Individually, usually, our old tweets are pretty insignificant. In aggregate, however, they paint a pretty complete picture of a person&rsquo;s random, unintentionally telling thoughts. This is the problem.</p>
<p>The assumption made of things written in social media and on Twitter specifically is a very different assumption than you might make about someone&rsquo;s notepad scribble from last week. I&rsquo;m not endeavoring to speculate why - I&rsquo;ve just seen enough cases of someone getting publicly flogged for something they posted years ago to know that it does happen. This is weird. If you wouldn&rsquo;t assume that a notepad scribble from last week or a crayon drawing from decades ago reflects the essence of who someone is <em>now,</em> why would you assume that an old tweet does?</p>
<p>You are not the same person you were last month - you&rsquo;ve seen things, read things, understood and learned things that have, in some small way, changed you. While a person may have the same sense of self and identity through most of their life, even this grows and changes over the years. We change our opinions, our desires, our habits. We are not stagnant beings, and we should not let ourselves be represented as such, however unintentionally.</p>
<h1 id="ephemeral-tweets">Ephemeral tweets</h1>
<p>If you look at my Twitter profile page today, you&rsquo;ll see fewer tweets there than you have fingers (I hope). I&rsquo;m using <a href="https://github.com/victoriadotdev/ephemeral">ephemeral</a> - a lightweight utility I wrote for use on <a href="https://aws.amazon.com/lambda/">AWS Lambda</a> - to delete all my tweets older than a few days. I&rsquo;m doing this for the same reason that I don&rsquo;t hang on to stuff that I no longer use - that stuff isn&rsquo;t relevant to me anymore. It doesn&rsquo;t represent me, either.</p>
<p>The code that makes up ephemeral is written in Go. AWS Lambda creates an environment for each Lambda function, so ephemeral utilizes environment variables for your private Twitter API keys and the maximum age of the tweets you want to keep, represented in hours, like <code>72h</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-golang" data-lang="golang"><span style="display:flex;"><span><span style="color:#66d9ef">var</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">consumerKey</span>       = <span style="color:#a6e22e">getenv</span>(<span style="color:#e6db74">&#34;TWITTER_CONSUMER_KEY&#34;</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">consumerSecret</span>    = <span style="color:#a6e22e">getenv</span>(<span style="color:#e6db74">&#34;TWITTER_CONSUMER_SECRET&#34;</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">accessToken</span>       = <span style="color:#a6e22e">getenv</span>(<span style="color:#e6db74">&#34;TWITTER_ACCESS_TOKEN&#34;</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">accessTokenSecret</span> = <span style="color:#a6e22e">getenv</span>(<span style="color:#e6db74">&#34;TWITTER_ACCESS_TOKEN_SECRET&#34;</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">maxTweetAge</span>       = <span style="color:#a6e22e">getenv</span>(<span style="color:#e6db74">&#34;MAX_TWEET_AGE&#34;</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">logger</span>            = <span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">New</span>()
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">getenv</span>(<span style="color:#a6e22e">name</span> <span style="color:#66d9ef">string</span>) <span style="color:#66d9ef">string</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">v</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Getenv</span>(<span style="color:#a6e22e">name</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">v</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;&#34;</span> {
</span></span><span style="display:flex;"><span>		panic(<span style="color:#e6db74">&#34;missing required environment variable &#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">name</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">v</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The program uses the <a href="https://github.com/ChimeraCoder/anaconda">anaconda</a> library. It fetches your timeline up to the Twitter API&rsquo;s limit of 200 tweets per request, then compares each tweet&rsquo;s date of creation to your <code>MAX_TWEET_AGE</code> variable to decide whether it&rsquo;s old enough to be deleted. After deleting all the expired tweets, the Lambda function terminates.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-golang" data-lang="golang"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">deleteFromTimeline</span>(<span style="color:#a6e22e">api</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">anaconda</span>.<span style="color:#a6e22e">TwitterApi</span>, <span style="color:#a6e22e">ageLimit</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Duration</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">timeline</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">getTimeline</span>(<span style="color:#a6e22e">api</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#e6db74">&#34;Could not get timeline&#34;</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">t</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">timeline</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">createdTime</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">CreatedAtTime</span>()
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#e6db74">&#34;Couldn&#39;t parse time &#34;</span>, <span style="color:#a6e22e">err</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">if</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Since</span>(<span style="color:#a6e22e">createdTime</span>) &gt; <span style="color:#a6e22e">ageLimit</span> {
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">api</span>.<span style="color:#a6e22e">DeleteTweet</span>(<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Id</span>, <span style="color:#66d9ef">true</span>)
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;DELETED: Age - &#34;</span>, <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Since</span>(<span style="color:#a6e22e">createdTime</span>).<span style="color:#a6e22e">Round</span>(<span style="color:#ae81ff">1</span><span style="color:#f92672">*</span><span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Minute</span>), <span style="color:#e6db74">&#34; - &#34;</span>, <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Text</span>)
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>					<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#e6db74">&#34;Failed to delete! &#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>				}
</span></span><span style="display:flex;"><span>			}
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;No more tweets to delete.&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Read the full code <a href="https://github.com/victoriadotdev/ephemeral/blob/master/main.go">here</a>.</p>
<p>For a use case like this, AWS Lambda has a free tier that costs nothing. If you&rsquo;re any level of developer, it&rsquo;s an extremely useful tool to become familiar with. For a full walkthrough with screenshots of how to set up a Lambda function that tweets for you, you can read <a href="https://victoria.dev/verbose/aws-lambda/">this article</a>. The set up for ephemeral is the same, it just has an opposite function. :)</p>
<p>I forked ephemeral from Adam Drake&rsquo;s <a href="https://github.com/adamdrake/harold">Harold</a>, a Twitter tool that has many useful functions beyond keeping your timeline trimmed. If you have more than 200 tweets to delete at first pass, please use Harold to do that first. You can run Harold with the <code>deletetimeline</code> flag from your terminal.</p>
<p>For sentiment, you may like to <a href="https://twitter.com/settings/your_twitter_data">download all your tweets before deleting them</a>.</p>
<h1 id="why-use-twitter-at-all">Why use Twitter at all?</h1>
<p>In anticipation of the question, let me say that yes, I do use Twitter besides just as a bucket for my Lambda functions to fill and empty. It has its benefits, most related to what I perceive to be its original intended purpose: to be a means of near-instant communication for short, digestible pieces of information reaching a widespread pool of people.</p>
<p>I use it as a way to keep tabs on what&rsquo;s happening <em>right now.</em> I use it to comment on, joke about, and commiserate with things tweeted by the people I follow <em>right now.</em> By keeping my timeline restricted to only the most recent few days, I feel like I&rsquo;m using Twitter more like it was meant to be used: a way to join the conversation and see what&rsquo;s happening in the world <em>right now</em> - instead of just another place to amass more &ldquo;stuff.&rdquo;</p>
]]></content></entry><entry><title type="html">Running a free Twitter bot on AWS Lambda</title><link href="https://victoria.dev/archive/running-a-free-twitter-bot-on-aws-lambda/"/><id>https://victoria.dev/archive/running-a-free-twitter-bot-on-aws-lambda/</id><author><name>Victoria Drake</name></author><published>2018-03-05T10:29:15-05:00</published><updated>2018-03-05T10:29:15-05:00</updated><content type="html"><![CDATA[<p>If you read <a href="/blog/about-time/">About time</a>, you&rsquo;ll know that I&rsquo;m a big believer in spending time now on building things that save time in the future. To this end I built a simple Twitter bot in Go that would occasionally post links to my articles and keep my account interesting even when I&rsquo;m too busy to use it. The tweets help drive traffic to my sites, and I don&rsquo;t have to lift a finger.</p>
<p>I ran the bot on an Amazon EC2 instance for about a month. My AWS usage has historically been pretty inexpensive (less than the price of a coffee in most of North America), so I was surprised when the little instance I was using racked up a bill 90% bigger than the month before. I don&rsquo;t think AWS is expensive, to be clear, but still&hellip; I&rsquo;m cheap. I want my Twitter bot, and I want it for less.</p>
<p>I&rsquo;d been meaning to explore AWS Lamda, and figured this was a good opportunity. Unlike an EC2 instance that is constantly running (and charging you for it), Lambda charges you per request and according to the duration of time your function takes to run. There&rsquo;s a free tier, too, and the first 1 million requests, plus a certain amount of compute time, are free. Roughly translated to running a Twitter bot that posts for you, say, twice a day, your monthly cost for using Lambda would total&hellip; carry the one&hellip; nothing. I&rsquo;ve been running my Lambda function for a couple weeks now, completely free.</p>
<p>When recently it came to me to take the reigns of the <a href="https://twitter.com/freeCodeCampTO">@freeCodeCampTO</a> Twitter, I decided to employ a similar strategy, and also use this opportunity to document the process for you, dear reader.</p>
<p>So if you&rsquo;re currently using a full-time running instance for a task that could be served by a cron job, this is the article for you. I&rsquo;ll cover how to write your function for Lambda, how to get it set up to run automatically, and as a sweet little bonus, a handy bash script that updates your function from the command line whenever you need to make a change. Let&rsquo;s do it!</p>
<h2 id="is-lambda-right-for-you">Is Lambda right for you</h2>
<p>When I wrote the code for my Twitter bot in Go, I intended to have it run on an AWS instance and borrowed heavily from <a href="https://github.com/campoy/justforfunc/tree/master/14-twitterbot">Francesc&rsquo;s awesome Just for Func episode</a>. Some time later I modified it to randomly choose an article from my RSS feeds and tweet the link, twice a day. I wanted to do something similar for the @freeCodeCampTO bot, and have it tweet an inspiring quote about programming every morning.</p>
<p>This is a good use case for Lambda because:</p>
<ul>
<li>The program should execute once</li>
<li>It runs on a regular schedule, using time as a trigger</li>
<li>It doesn&rsquo;t need to run constantly</li>
</ul>
<p>The important thing to keep in mind is that Lambda runs a function once in response to an event that you define. The most widely applicable trigger is a simple cron expression, but there are many other trigger events you can hook up. You can get an overview <a href="https://aws.amazon.com/lambda/">here</a>.</p>
<h2 id="write-a-lambda-function">Write a Lambda function</h2>
<p>I found this really straightforward to do in Go. First, grab the <a href="https://github.com/aws/aws-lambda-go">aws-lambda-go</a> library:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>go get github.com/aws/aws-lambda-go/lambda
</span></span></code></pre></div><p>Then make this your <code>func main()</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">lambda</span>.<span style="color:#a6e22e">Start</span>(<span style="color:#a6e22e">tweetFeed</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Where <code>tweetFeed</code> is the name of the function that makes everything happen. While I won&rsquo;t go into writing the whole Twitter bot here, you can view my code <a href="https://gist.github.com/victoriadrake/7859dab68df87e28f40d6715d08383c7">on GitHub</a>.</p>
<h2 id="setting-up-aws-lambda">Setting up AWS Lambda</h2>
<p>I&rsquo;m assuming you already have an AWS account. If not, first things first here: <a href="https://aws.amazon.com/free">https://aws.amazon.com/free</a></p>
<h3 id="1-create-your-function">1. Create your function</h3>
<p>Find AWS Lambda in the list of services, then look for this shiny button:</p>
<p><img src="lambda-01.png#screenshot" alt="Create function"></p>
<p>We&rsquo;re going to author a function from scratch. Name your function, then under <strong>Runtime</strong> choose &ldquo;Go 1.x&rdquo;.</p>
<p>Under <strong>Role name</strong> write any name you like. It&rsquo;s a required field but irrelevant for this use case.</p>
<p>Click <strong>Create function.</strong></p>
<p><img src="lambda-02.png#screenshot" alt="Author from scratch"></p>
<h3 id="2-configure-your-function">2. Configure your function</h3>
<p>You&rsquo;ll see a screen for configuring your new function. Under <strong>Handler</strong> enter the name of your Go program.</p>
<p><img src="lambda-03.png#screenshot" alt="Configure your function"></p>
<p>If you scroll down, you&rsquo;ll see a spot to enter environment variables. This is a great place to enter the Twitter API tokens and secrets, using the variable names that your program expects. The AWS Lambda function will create the environment for you using the variables you provide here.</p>
<p><img src="lambda-04.png#screenshot" alt="Environment variables"></p>
<p>No further settings are necessary for this use case. Click <strong>Save</strong> at the top of the page.</p>
<h3 id="3-upload-your-code">3. Upload your code</h3>
<p>You can upload your function code as a zip file on the configuration screen. Since we&rsquo;re using Go, you&rsquo;ll want to <code>go build</code> first, then zip the resulting executable before uploading that to Lambda.</p>
<p>&hellip;Of course I&rsquo;m not going to do that manually every time I want to tweak my function. That&rsquo;s what <code>awscli</code> and this bash script is for!</p>
<p><code>update.sh</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>go build <span style="color:#f92672">&amp;&amp;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>zip fcc-tweet.zip fcc-tweet <span style="color:#f92672">&amp;&amp;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>rm fcc-tweet <span style="color:#f92672">&amp;&amp;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>aws lambda update-function-code --function-name fcc-tweet --zip-file fileb://fcc-tweet.zip <span style="color:#f92672">&amp;&amp;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>rm fcc-tweet.zip
</span></span></code></pre></div><p>Now whenever I make a tweak, I just run <code>bash update.sh</code>.</p>
<p>If you&rsquo;re not already using <a href="https://aws.amazon.com/cli/">AWS Command Line Interface</a>, do <code>pip install awscli</code> and thank me later. Find instructions for getting set up and configured in a few minutes <a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html">here</a> under <strong>Quick Configuration</strong>.</p>
<h3 id="4-test-your-function">4. Test your function</h3>
<p>Wanna see it go? Of course you do! Click &ldquo;Configure test events&rdquo; in the dropdown at the top.</p>
<p><img src="lambda-05.png#screenshot" alt="Configure test events"></p>
<p>Since you&rsquo;ll use a time-based trigger for this function, you don&rsquo;t need to enter any code to define test events in the popup window. Simply write any name under <strong>Event name</strong> and empty the JSON in the field below. Then click <strong>Create</strong>.</p>
<p><img src="lambda-06.png#screenshot" alt="Configuring an empty test event"></p>
<p>Click <strong>Test</strong> at the top of the page, and if everything is working correctly you should see&hellip;</p>
<p><img src="lambda-07.png#screenshot" alt="Test success notification"></p>
<h3 id="5-set-up-cloudwatch-events">5. Set up CloudWatch Events</h3>
<p>To run our function as we would a cron job - as a regularly scheduled time-based event - we&rsquo;ll use CloudWatch. Click <strong>CloudWatch Events</strong> in the <strong>Designer</strong> sidebar.</p>
<p><img src="lambda-08.png#screenshot" alt="CloudWatch Events trigger"></p>
<p>Under <strong>Configure triggers</strong>, you&rsquo;ll create a new rule. Choose a descriptive name for your rule without spaces or punctuation, and ensure <strong>Schedule expression</strong> is selected. Then input the time you want your program to run as a <em>rate expression</em>, or cron expression.</p>
<p>A cron expression looks like this: <code>cron(0 12 * * ? *)</code></p>
<table>
<thead>
<tr>
<th>Minutes</th>
<th>Hours</th>
<th>Month</th>
<th>Day of week</th>
<th>Year</th>
<th>In English</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>12</td>
<td><code>*</code></td>
<td>?</td>
<td><code>*</code></td>
<td>Run at noon (UTC) every day</td>
</tr>
</tbody>
</table>
<p>For more on how to write your cron expressions, read <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html">this.</a></p>
<p>If you want your program to run twice a day, say once at 10am and again at 3pm, you&rsquo;ll need to set two separate CloudWatch Events triggers and cron expression rules.</p>
<p>Click <strong>Add</strong>.</p>
<p><img src="lambda-09.png#screenshot" alt="Set cron expression rule"></p>
<h2 id="watch-it-go">Watch it go</h2>
<p>That&rsquo;s all you need to get your Lambda function up and running! Now you can sit back, relax, and do more important things than share your RSS links on Twitter.</p>
]]></content></entry><entry><title type="html">Moving to a new domain without breaking old links with AWS &amp;amp; Disqus</title><link href="https://victoria.dev/archive/moving-to-a-new-domain-without-breaking-old-links-with-aws-disqus/"/><id>https://victoria.dev/archive/moving-to-a-new-domain-without-breaking-old-links-with-aws-disqus/</id><author><name>Victoria Drake</name></author><published>2018-01-10T08:56:20-05:00</published><updated>2018-01-10T08:56:20-05:00</updated><content type="html"><![CDATA[<p>I started blogging about my nomadic travels last year, and so far the habit has stuck. Like all side projects, I won&rsquo;t typically invest heavily in setting up web properties before I can be reasonably certain that such an investment is worth my time or enjoyment. In other words: don&rsquo;t buy the domain until you&rsquo;ve proven to yourself that you&rsquo;ll stick with it!</p>
<p>After some months of regular posting I felt I was ready to commit (short courtship, I know, but we&rsquo;re all adults here) and I bought a dedicated domain, <a href="https://heronebag.com">herOneBag.com</a>.</p>
<p>Up until recently, my #NomadLyfe blog was just a subdirectory of my main personal site. Now it&rsquo;s all grown up and ready to strike out into the world alone! Here&rsquo;s the setup for the site:</p>
<ul>
<li>Static site in Amazon Web Services S3 bucket</li>
<li>Route 53 handling the DNS</li>
<li>CloudFront for distribution and a custom SSL certificate</li>
<li>Disqus for comments</li>
</ul>
<p>If you&rsquo;d like a walk-through for how to set up a new domain with this structure, it&rsquo;s over here: <a href="https://victoria.dev/verbose/aws-static-site/">Hosting your static site with AWS S3, Route 53, and CloudFront</a>. In this post, I&rsquo;ll just detail how I managed to move my blog to the new site without breaking the old links or losing any comments.</p>
<h2 id="preserve-old-links-with-redirection-rules">Preserve old links with redirection rules</h2>
<p>I wanted to avoid breaking links that have been posted around the web by forwarding visitors to the new URL. The change looks like this:</p>
<p>Old URL: <code>https://victoria.dev/meta/5-bag-lessons/</code></p>
<p>New URL: <code>https://heronebag.com/blog/5-bag-lessons/</code></p>
<p>You can see that the domain name as well as the subdirectory have changed, but the slug for the blog post remains the same. (I love static sites.)</p>
<p>To redirect links from the old site, we&rsquo;ll need to set redirection rules in the old site&rsquo;s S3 bucket. AWS provides a way to set up a conditional redirect. This is set in the &ldquo;Redirection rules&rdquo; section of your S3 bucket&rsquo;s properties, under &ldquo;Static website hosting.&rdquo; You can <a href="https://docs.aws.amazon.com/AmazonS3/latest/dev/how-to-page-redirect.html#advanced-conditional-redirects">find the documentation here.</a></p>
<p><img src="aws-redirect.png#screenshot" alt="Redirection rules placement"></p>
<p>There are a few examples given, but none that represent the redirect I want. In addition to changing the prefix of the object key, we&rsquo;re also changing the domain. The latter is achieved with the <code>&lt;HostName&gt;</code> tag.</p>
<p>To redirect requests for the old blog URL to the new top level domain, we&rsquo;ll use the code below.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#f92672">&lt;RoutingRules&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&lt;RoutingRule&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;Condition&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&lt;KeyPrefixEquals&gt;</span>oldblog/<span style="color:#f92672">&lt;/KeyPrefixEquals&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;/Condition&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;Redirect&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&lt;HostName&gt;</span>newdomain.com<span style="color:#f92672">&lt;/HostName&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&lt;ReplaceKeyPrefixWith&gt;</span>newblog/<span style="color:#f92672">&lt;/ReplaceKeyPrefixWith&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;/Redirect&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&lt;/RoutingRule&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">&lt;/RoutingRules&gt;</span>
</span></span></code></pre></div><p>This rule ensures that requests for <code>olddomain.com/oldblog/specific-blog-post</code> will redirect to <code>newdomain.com/newblog/specific-blog-post</code>.</p>
<h2 id="migrate-disqus-comments">Migrate Disqus comments</h2>
<p>Disqus provides a tool for migrating the comment threads from your old blog site to the new one. You can find it in your Disqus admin tools at <code>your-short-name.disqus.com/admin/discussions/migrate/</code>.</p>
<p>To migrate posts from the old blog address to the new one, we&rsquo;ll use the URL mapper tool. Click &ldquo;Start URL mapper,&rdquo; then &ldquo;you can download a CSV here.&rdquo;</p>
<p><img src="aws-disqus.png#screenshot" alt="URL mapping for Disqus."></p>
<p>Disqus has decent instructions for how this tool works, and you can <a href="https://help.disqus.com/customer/en/portal/articles/912757-url-mapper">read them here.</a> Basically, you&rsquo;ll input the new blog URLs into the second column of the CSV file you downloaded, then pass it back to Disqus to process. If you&rsquo;re using a program to edit the CSV, be sure to save the resulting file in CSV format.</p>
<p>Unless you have a bazillion URLs, the tool works pretty quickly, and you&rsquo;ll get an email when it&rsquo;s finished. Don&rsquo;t forget to update the name of your site in the Disqus admin, too.</p>
<h2 id="transfer-other-settings">Transfer other settings</h2>
<p>Update links in your social profiles and any other sites you may have around the web. If you&rsquo;re using other services attached to your website like Google Analytics or IFTTT, don&rsquo;t forget to update those details too!</p>
]]></content></entry><entry><title type="html">A Unicode substitution cipher algorithm</title><link href="https://victoria.dev/archive/a-unicode-substitution-cipher-algorithm/"/><id>https://victoria.dev/archive/a-unicode-substitution-cipher-algorithm/</id><author><name>Victoria Drake</name></author><published>2018-01-06T20:00:28-05:00</published><updated>2018-01-06T20:00:28-05:00</updated><content type="html"><![CDATA[<p>Full transparency: I occasionally waste time messing around on Twitter. <em>(Gasp! Shock!)</em> One of the ways I waste time messing around on Twitter is by writing my name in my profile with different Unicode character &ldquo;fonts,&rdquo; 𝖑𝖎𝖐𝖊 𝖙𝖍𝖎𝖘 𝖔𝖓𝖊. I previously did this by searching for different Unicode characters on Google, then one-by-one copying and pasting them into the &ldquo;Name&rdquo; field on my Twitter profile. Since this method of wasting time was a bit of a time waster, I decided (in true programmer fashion) to write a tool that would help me save some time while wasting it.</p>
<p>I originally dubbed the tool &ldquo;uni-pretty,&rdquo; (based on LEGO&rsquo;s Unikitty from a movie &ndash; a pun that absolutely no one got) but have since renamed it <a href="http://victoria.dev/fancy-unicode/">fancy unicode</a>. It builds from <a href="https://github.com/victoriadrake/fancy-unicode">this GitHub repo</a>. It lets you type any characters into a field and then converts them into Unicode characters that also represent letters, giving you fancy &ldquo;fonts&rdquo; that override a website&rsquo;s CSS, like in your Twitter profile. (Sorry, Internet.)</p>
<p><img src="screenshot.png#screenshot" alt="fancy-unicode screenshot"></p>
<p>The tool&rsquo;s first naive iteration existed for about twenty minutes while I copy-pasted Unicode characters into a data structure. This approach of storing the characters in the JavaScript file, called hard-coding, is fraught with issues. Besides having to store every character from every font style, it&rsquo;s painstaking to build, hard to update, and more code means it&rsquo;s susceptible to more possible errors.</p>
<p>Fortunately, working with Unicode means that there&rsquo;s a way to avoid the whole mess of having to store all the font characters: Unicode numbers are sequential. More importantly, the special characters in Unicode that could be used as fonts (meaning that there&rsquo;s a matching character for most or all of the letters of the alphabet) are always in the following sequence: capital A-Z, lowercase a-z.</p>
<p>For example, in the fancy Unicode above, the lowercase letter &ldquo;L&rdquo; character has the Unicode number <code>U+1D591</code> and HTML code <code>&amp;#120209;</code>. The next letter in the sequence, a lowercase letter &ldquo;M,&rdquo; has the Unicode number <code>U+1D592</code> and HTML code <code>&amp;#120210;</code>. Notice how the numbers in those codes increment by one.</p>
<p>Why&rsquo;s this relevant? Since each special character can be referenced by a number, and we know that the order of the sequence is always the same (capital A-Z, lowercase a-z), we&rsquo;re able to produce any character simply by knowing the first number of its font sequence (the capital &ldquo;A&rdquo;). If this reminds you of anything, you can borrow my decoder pin.</p>
<p>In cryptography, the Caesar cipher (or shift cipher) is a simple method of encryption that utilizes substitution of one character for another in order to encode a message. This is typically done using the alphabet and a shift &ldquo;key&rdquo; that tells you which letter to substitute for the original one. For example, if I were trying to encode the word &ldquo;cat&rdquo; with a right shift of 3, it would look like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>c a t
</span></span><span style="display:flex;"><span>f d w
</span></span></code></pre></div><p>With this concept, encoding our plain text letters as a Unicode &ldquo;font&rdquo; is a simple process. All we need is an array to reference our plain text letters with, and the first index of our Unicode capital &ldquo;A&rdquo; representation. Since some Unicode numbers also include letters (which are sequential, but an unnecessary complication) and since the intent is to display the page in HTML, we&rsquo;ll use the HTML code number <code>&amp;#120172;</code>, with the extra bits removed for brevity.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">plain</span> <span style="color:#f92672">=</span> [<span style="color:#e6db74">&#39;A&#39;</span>, <span style="color:#e6db74">&#39;B&#39;</span>, <span style="color:#e6db74">&#39;C&#39;</span>, <span style="color:#e6db74">&#39;D&#39;</span>, <span style="color:#e6db74">&#39;E&#39;</span>, <span style="color:#e6db74">&#39;F&#39;</span>, <span style="color:#e6db74">&#39;G&#39;</span>, <span style="color:#e6db74">&#39;H&#39;</span>, <span style="color:#e6db74">&#39;I&#39;</span>, <span style="color:#e6db74">&#39;J&#39;</span>, <span style="color:#e6db74">&#39;K&#39;</span>, <span style="color:#e6db74">&#39;L&#39;</span>, <span style="color:#e6db74">&#39;M&#39;</span>, <span style="color:#e6db74">&#39;N&#39;</span>, <span style="color:#e6db74">&#39;O&#39;</span>, <span style="color:#e6db74">&#39;P&#39;</span>, <span style="color:#e6db74">&#39;Q&#39;</span>, <span style="color:#e6db74">&#39;R&#39;</span>, <span style="color:#e6db74">&#39;S&#39;</span>, <span style="color:#e6db74">&#39;T&#39;</span>, <span style="color:#e6db74">&#39;U&#39;</span>, <span style="color:#e6db74">&#39;V&#39;</span>, <span style="color:#e6db74">&#39;W&#39;</span>, <span style="color:#e6db74">&#39;X&#39;</span>, <span style="color:#e6db74">&#39;Y&#39;</span>, <span style="color:#e6db74">&#39;Z&#39;</span>, <span style="color:#e6db74">&#39;a&#39;</span>, <span style="color:#e6db74">&#39;b&#39;</span>, <span style="color:#e6db74">&#39;c&#39;</span>, <span style="color:#e6db74">&#39;d&#39;</span>, <span style="color:#e6db74">&#39;e&#39;</span>, <span style="color:#e6db74">&#39;f&#39;</span>, <span style="color:#e6db74">&#39;g&#39;</span>, <span style="color:#e6db74">&#39;h&#39;</span>, <span style="color:#e6db74">&#39;i&#39;</span>, <span style="color:#e6db74">&#39;j&#39;</span>, <span style="color:#e6db74">&#39;k&#39;</span>, <span style="color:#e6db74">&#39;l&#39;</span>, <span style="color:#e6db74">&#39;m&#39;</span>, <span style="color:#e6db74">&#39;n&#39;</span>, <span style="color:#e6db74">&#39;o&#39;</span>, <span style="color:#e6db74">&#39;p&#39;</span>, <span style="color:#e6db74">&#39;q&#39;</span>, <span style="color:#e6db74">&#39;r&#39;</span>, <span style="color:#e6db74">&#39;s&#39;</span>, <span style="color:#e6db74">&#39;t&#39;</span>, <span style="color:#e6db74">&#39;u&#39;</span>, <span style="color:#e6db74">&#39;v&#39;</span>, <span style="color:#e6db74">&#39;w&#39;</span>, <span style="color:#e6db74">&#39;x&#39;</span>, <span style="color:#e6db74">&#39;y&#39;</span>, <span style="color:#e6db74">&#39;z&#39;</span>];
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">fancyA</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">120172</span>;
</span></span></code></pre></div><p>Since we know that the letter sequence of the fancy Unicode is the same as our plain text array, any letter can be found by using its index in the plain text array as an offset from the fancy capital &ldquo;A&rdquo; number. For example, capital &ldquo;B&rdquo; in fancy Unicode is the capital &ldquo;A&rdquo; number, <code>120172</code> plus B&rsquo;s index, which is <code>1</code>: <code>120173</code>.</p>
<p>Here&rsquo;s our conversion function:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">convert</span>(<span style="color:#a6e22e">string</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Create a variable to store our converted letters
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">converted</span> <span style="color:#f92672">=</span> [];
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Break string into substrings (letters)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">arr</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">string</span>.<span style="color:#a6e22e">split</span>(<span style="color:#e6db74">&#39;&#39;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Search plain array for indexes of letters
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">arr</span>.<span style="color:#a6e22e">forEach</span>(<span style="color:#a6e22e">element</span> =&gt; {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">plain</span>.<span style="color:#a6e22e">indexOf</span>(<span style="color:#a6e22e">element</span>);
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// If the letter isn&#39;t a letter (not found in the plain array)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">i</span> <span style="color:#f92672">==</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">// Return as a whitespace
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>            <span style="color:#a6e22e">converted</span>.<span style="color:#a6e22e">push</span>(<span style="color:#e6db74">&#39; &#39;</span>);
</span></span><span style="display:flex;"><span>        } <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">// Get relevant character from fancy number + index
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>            <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">unicode</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">fancyA</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">i</span>;
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">// Return as HTML code
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>            <span style="color:#a6e22e">converted</span>.<span style="color:#a6e22e">push</span>(<span style="color:#e6db74">&#39;&amp;#&#39;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">unicode</span> <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39;;&#39;</span>);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    });
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Print the converted letters as a string
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#a6e22e">converted</span>.<span style="color:#a6e22e">join</span>(<span style="color:#e6db74">&#39;&#39;</span>));
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>A neat possibility for this method of encoding requires a departure from my original purpose, which was to create a human-readable representation of the original string. If the purpose was instead to produce a cipher, this could be done by using any Unicode index in place of <code>fancyA</code> as long as the character indexed isn&rsquo;t a representation of a capital &ldquo;A.&rdquo;</p>
<p>Here&rsquo;s the same code set up with a simplified plain text array, and a non-letter-representation Unicode key:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">plain</span> <span style="color:#f92672">=</span> [<span style="color:#e6db74">&#39;a&#39;</span>, <span style="color:#e6db74">&#39;b&#39;</span>, <span style="color:#e6db74">&#39;c&#39;</span>, <span style="color:#e6db74">&#39;d&#39;</span>, <span style="color:#e6db74">&#39;e&#39;</span>, <span style="color:#e6db74">&#39;f&#39;</span>, <span style="color:#e6db74">&#39;g&#39;</span>, <span style="color:#e6db74">&#39;h&#39;</span>, <span style="color:#e6db74">&#39;i&#39;</span>, <span style="color:#e6db74">&#39;j&#39;</span>, <span style="color:#e6db74">&#39;k&#39;</span>, <span style="color:#e6db74">&#39;l&#39;</span>, <span style="color:#e6db74">&#39;m&#39;</span>, <span style="color:#e6db74">&#39;n&#39;</span>, <span style="color:#e6db74">&#39;o&#39;</span>, <span style="color:#e6db74">&#39;p&#39;</span>, <span style="color:#e6db74">&#39;q&#39;</span>, <span style="color:#e6db74">&#39;r&#39;</span>, <span style="color:#e6db74">&#39;s&#39;</span>, <span style="color:#e6db74">&#39;t&#39;</span>, <span style="color:#e6db74">&#39;u&#39;</span>, <span style="color:#e6db74">&#39;v&#39;</span>, <span style="color:#e6db74">&#39;w&#39;</span>, <span style="color:#e6db74">&#39;x&#39;</span>, <span style="color:#e6db74">&#39;y&#39;</span>, <span style="color:#e6db74">&#39;z&#39;</span>];
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">key</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">9016</span>;
</span></span></code></pre></div><p>You might be able to imagine that decoding a cipher produced by this method would be relatively straightforward, once you knew the encoding secret. You&rsquo;d simply need to subtract the key from the HTML code numbers of the encoded characters, then find the relevant plain text letters at the remaining indexes.</p>
<p>Well, that&rsquo;s it for today. Be sure to drink your Ovaltine and we&rsquo;ll see you right here next Monday at 5:45!</p>
<p>Oh, and&hellip; ⍔⍠⍟⍘⍣⍒⍥⍦⍝⍒⍥⍚⍠⍟⍤ ⍒⍟⍕ ⍨⍖⍝⍔⍠⍞⍖ ⍥⍠ ⍥⍙⍖ ⍔⍣⍪⍡⍥⍚⍔ ⍦⍟⍚⍔⍠⍕⍖ ⍤⍖⍔⍣⍖⍥ ⍤⍠⍔⍚⍖⍥⍪</p>
<p>:)</p>
]]></content></entry><entry><title type="html">Hosting your static site with AWS S3, Route 53, and CloudFront</title><link href="https://victoria.dev/archive/hosting-your-static-site-with-aws-s3-route-53-and-cloudfront/"/><id>https://victoria.dev/archive/hosting-your-static-site-with-aws-s3-route-53-and-cloudfront/</id><author><name>Victoria Drake</name></author><published>2017-12-13T20:46:12-05:00</published><updated>2020-11-14T20:46:12-05:00</updated><content type="html"><![CDATA[<p>Some time ago I decided to stop freeloading on GitHub pages and move one of my sites to Amazon Web Services (AWS). It turns out that I&rsquo;m still mostly freeloading (yay free tier) so it amounted to a learning experience. Here are the components that let me host and serve the site at my custom domain with HTTPS.</p>
<ul>
<li>Static site in Amazon Web Services S3 bucket</li>
<li>Route 53 handling the DNS</li>
<li>CloudFront for distribution and a custom SSL certificate</li>
</ul>
<p>I set all that up most of a year ago. At the time, I found the AWS documentation to be rather fragmented and inconvenient to follow - it was hard to find what you were looking for without knowing what a specific setting might be called, or where it was, or if it existed at all. When I recently set up a new site and stumbled through this process again, I didn&rsquo;t find it any easier. Hopefully this post can help to collect the relevant information into a more easily followed process and serve as an accompanying guide to save future me (and you) some time.</p>
<p>Rather than replace existing documentation, this post is meant to supplement it. Think of me as your cool tech-savvy friend on the phone with you at 4am, troubleshooting your website. (Please don&rsquo;t actually call me at 4am.) I&rsquo;ll walk through the set up while providing links for the documentation that was ultimately helpful (mostly so I can find it again later&hellip;).</p>
<h2 id="hosting-a-static-site-with-amazon-s3-and-a-custom-domain">Hosting a static site with Amazon S3 and a custom domain</h2>
<p>If you&rsquo;re starting from scratch, you&rsquo;ll need an AWS account. It behooves you to get one, even if you don&rsquo;t like paying for services - there&rsquo;s a free tier that will cover most of the experimental stuff you&rsquo;re going to want to do in the first year, and even the things I do pay for cost me less than a dollar a month. You can sign up at <a href="https://aws.amazon.com/free">https://aws.amazon.com/free</a>.</p>
<p>Getting your static site hosted and available at your custom domain is your first mission, should you choose to accept it. <a href="http://docs.aws.amazon.com/AmazonS3/latest/dev/website-hosting-custom-domain-walkthrough.html">Your instructions are here.</a></p>
<p>Creating the buckets for site hosting on S3 is the most straightforward part of this process in my opinion, and the AWS documentation walkthrough covers what you&rsquo;ll need to do quite well. It gets a little unclear around <em>Step 3: Create and Configure Amazon Route 53 Hosted Zone</em>, so come back and read on once you&rsquo;ve reached that point. I&rsquo;ll make some tea in the meantime.</p>
<p>&hellip; 🎶 🎵</p>
<p>Ready? Cool. See, I&rsquo;m here for you.</p>
<h2 id="set-up-route-53">Set up Route 53</h2>
<p>The majority of the work in this section amounts to creating the correct record sets for your custom domain. If you&rsquo;re already familiar with how record sets work, the documentation is a bit of a slog. Here&rsquo;s how it should look when you&rsquo;re finished:</p>
<p><img src="aws-recordsets.png#screenshot" alt="Route 53 record sets."></p>
<p>The &ldquo;NS&rdquo; and &ldquo;SOA&rdquo; records are created automatically for you. The only records you need to create are the &ldquo;A&rdquo; records.</p>
<p>Hop over to Route 53 and follow <a href="http://docs.aws.amazon.com/Route53/latest/DeveloperGuide/MigratingDNS.html">this walkthrough</a> to create a &ldquo;hosted zone.&rdquo; The value of the <strong>NS</strong> (Name Servers) records are what you&rsquo;ll have to provide to your domain name registrar. Your registrar is wherever you bought your custom domain, such as this super subtle <a href="https://www.jdoqocy.com/ds70r09608OQPPRVXSQPOQSRVVVVX" target="_top">Namecheap.com affiliate link</a>
 right here. (Thanks for your support! 😊)</p>
<p>If you created two buckets in the first section (one for <code>yourdomain.com</code> and one for <code>www.yourdomain.com</code>), you&rsquo;ll need two separate A records in Route 53. Initially, these have the value of the endpoints for your matching S3 buckets (looks like <code>s3-website.us-east-2.amazonaws.com</code>). Later, you&rsquo;ll change them to your CloudFront domain name.</p>
<p>If you went with Namecheap as your registrar, <a href="http://docs.aws.amazon.com/AmazonS3/latest/dev/website-hosting-custom-domain-walkthrough.html#root-domain-walkthrough-update-ns-record">Step 4</a> looks like this:</p>
<p><img src="aws-namecheapdns.png#screenshot" alt="Namecheap&rsquo;s Custom DNS settings."></p>
<p>Waiting is the hardest part&hellip; I&rsquo;ve gotten into the habit of working on another project or setting up the DNS change before going to bed so that changes have time to propagate without me feeling like I need to fiddle with it. ^^;</p>
<p>When the transfer&rsquo;s ready, you&rsquo;ll see your site at <code>http://yourdomain.com</code>. Next, you&rsquo;ll want to set up CloudFront so that becomes <code>https://yourdomain.com</code>.</p>
<h2 id="set-up-cloudfront-and-ssl">Set up CloudFront and SSL</h2>
<p><a href="http://docs.aws.amazon.com/AmazonS3/latest/dev/website-hosting-cloudfront-walkthrough.html">Here are the instructions for setting up CloudFront.</a> There are a few important points to make sure you don&rsquo;t miss on the &ldquo;Create Distribution&rdquo; page:</p>
<ul>
<li><strong>Origin Domain Name:</strong> Make sure to use your S3 bucket endpoint, and not select the bucket from the dropdown menu that appears.</li>
<li><strong>Viewer Protocol Policy:</strong> If you want requests for <code>http://yourdomain.com</code> to always result in <code>https://yourdomain.com</code>, choose &ldquo;Redirect HTTP to HTTPS.&rdquo;</li>
<li><strong>Alternate Domain Names:</strong> Enter <code>yourdomain.com</code> and <code>www.yourdomain.com</code> on separate lines.</li>
<li><strong>SSL Certificate:</strong> See below.</li>
<li><strong>Default Root Object:</strong> Enter the name of the html file that should be returned when your users go to <code>https://yourdomain.com</code>. This is usually &ldquo;index.html&rdquo;.</li>
</ul>
<h3 id="ssl-certificate">SSL Certificate</h3>
<p>To show your content with HTTPS at your custom domain, you&rsquo;ll need to choose &ldquo;Custom SSL Certificate.&rdquo; You can easily get an SSL Certificate with AWS Certificate Manager. Click on &ldquo;Request or Import a Certificate with ACM&rdquo; to get started in a new window.</p>
<p><a href="http://docs.aws.amazon.com/acm/latest/userguide/gs-acm-request.html">Here are instructions for setting up a certificate.</a> I don&rsquo;t think they&rsquo;re very good, personally. Don&rsquo;t worry, I got you.</p>
<p>To account for &ldquo;<a href="https://www.yourdomain.com">www.yourdomain.com</a>&rdquo; as well as any subdomains, you&rsquo;ll want to add two domain names to the certificate, like so:</p>
<p><img src="aws-acmdomains.png#screenshot" alt="Adding domain names to ACM."></p>
<p>Click &ldquo;Next.&rdquo; You&rsquo;ll be asked to choose a validation method. Choose &ldquo;DNS validation&rdquo; and click &ldquo;Review.&rdquo; If everything is as it should be, click &ldquo;Confirm and request.&rdquo;</p>
<p>You&rsquo;ll see a page, &ldquo;Validation&rdquo; that looks like this. You&rsquo;ll have to click the little arrow next to both domain names to get the important information to show:</p>
<p><img src="aws-acmvalidation.png#screenshot" alt="Validation instructions for ACM."></p>
<p>Under both domain names, click the button for &ldquo;Create record in Route 53.&rdquo; This will automatically create a CNAME record set in Route 53 with the given values, which ACM will then check in order to validate that you own those domains. You could create the records manually, if you wanted to for some reason. I don&rsquo;t know, maybe you&rsquo;re killing time. ¯\_(ツ)_/¯</p>
<p>Click &ldquo;Continue.&rdquo; You&rsquo;ll see a console that looks like this:</p>
<p><img src="aws-acmcertificates.png#screenshot" alt="List of certificates you own."></p>
<p>It may take some time for the validation to complete, at which point the &ldquo;Pending validation&rdquo; status will change to &ldquo;Issued.&rdquo; Again with the waiting. You can close this window to return to the CloudFront set up. Once the certificate is validated, you&rsquo;ll see it in the dropdown menu under &ldquo;Custom SSL Certificate.&rdquo; You can click &ldquo;Create Distribution&rdquo; to finish setting up CloudFront.</p>
<p>In your CloudFront Distributions console, you&rsquo;ll see &ldquo;In Progress&rdquo; until AWS has done its thing. Once it&rsquo;s done, it&rsquo;ll change to &ldquo;Deployed.&rdquo;</p>
<h3 id="one-last-thing">One last thing</h3>
<p>Return to your <a href="https://console.aws.amazon.com/route53/">Route 53 console</a> and click on &ldquo;Hosted zones&rdquo; in the sidebar, then your domain name from the list. For both A records, change the &ldquo;Alias Target&rdquo; from the S3 endpoint to your CloudFront distribution domain, which should look something like <code>dj4p1rv6mvubz.cloudfront.net</code>. It appears in the dropdown after you clear the field.</p>
<h2 id="youre-done">You&rsquo;re done</h2>
<p>Well, usually. If you navigate to your new HTTPS domain and don&rsquo;t see your beautiful new site where it should be, here are some things you can do:</p>
<ol>
<li>Check S3 bucket policy - ensure that the bucket for <code>yourdomain.com</code> in the S3 console shows &ldquo;Public&rdquo; in the &ldquo;Access&rdquo; column.</li>
<li>Check S3 bucket index document - In the &ldquo;metadata&rdquo; tab for the bucket, then &ldquo;Static website hosting&rdquo;. Usually &ldquo;index.html&rdquo;.</li>
<li>Check CloudFront Origin - the &ldquo;Origin&rdquo; column in the CloudFront Console should show the S3 bucket&rsquo;s endpoint (<code>s3-website.us-east-2.amazonaws.com</code>), not the bucket name (<code>yourdomain.com.s3.amazonaws.com</code>).</li>
<li>Check CloudFront Default Root Object - clicking on the distribution name should take you to a details page that shows &ldquo;Default Root Object&rdquo; in the list with the value that you set, usually &ldquo;index.html&rdquo;.</li>
<li>Wait. Sometimes changes take up to 48hrs to propagate. ¯\_(ツ)_/¯</li>
</ol>
<p>I hope that helps you get set up with your new static site on AWS! If you found this post helpful, there&rsquo;s a lot more where this came from. You can subscribe below to see new posts first.</p>
<a href="https://medium.com/@victoriadotdev/subscribe" target="_blank" rel="noopener noreferrer" class="subscribe-button">
    <span class="subscribe-icon">📧</span>
    <span class="subscribe-text">Subscribe</span>
</a>

]]></content></entry><entry><title type="html">About time</title><link href="https://victoria.dev/archive/about-time/"/><id>https://victoria.dev/archive/about-time/</id><author><name>Victoria Drake</name></author><published>2017-11-22T14:05:14-05:00</published><updated>2017-11-22T14:05:14-05:00</updated><content type="html"><![CDATA[<p>This morning I read an article that&rsquo;s been making the rounds lately: <a href="https://nautil.us/modern-media-is-a-dos-attack-on-your-free-will-236806/">Modern Media Is a DoS Attack on Your Free Will</a>.</p>
<p>It&rsquo;s made me think, which I must admit, I at first didn&rsquo;t like. See, when I wake up in the morning (and subsequently wake up my computer) the first thing I do is go on Twitter to catch up on everything I missed while I was asleep. All this before my first coffee, mind you. Links on Twitter usually lead to stories on Medium, newly released apps on ProductHunt, and enticing sales on a new gadget or two on Amazon. Wherever it goes, in those blissfully half-awake mental recesses, the last thing I&rsquo;m trying to do is think.</p>
<p>However, yesterday, I also happened to listen to a podcast from freeCodeCamp. It was <a href="https://freecodecamp.libsyn.com/ep-7-the-code-im-still-ashamed-of">#7: The code I&rsquo;m still ashamed of</a>. This lead to thoughts on the responsibilities of programmers - the people tasked with designing and building apps and systems meant to steer the very course of your life.</p>
<p>This morning, the combined swirling mess of notions brought on by these two sources of information had, even before my first coffee, the unfortunate effect of making me think.</p>
<p>Mostly, I thought about intention, and time.</p>
<p>I don&rsquo;t believe it&rsquo;s wildly inaccurate to say that when you go about doing something in your daily life, you have a general awareness of your reason for doing it. If you leave your building and go down the street to Starbucks and buy a coffee, more often than not, it&rsquo;s because you wanted a coffee. If you go to the corner store and buy a litre of milk, you probably intend to drink it. If you find yourself nicely dressed on a Friday night waiting at a well-decorated restaurant to meet another human being with whom you share an apparent mutual attraction, I can risk a guess that you&rsquo;re after some form of pleasant human interaction.</p>
<p>In each of these, and many more examples you can think up, the end goal is clearly defined. There is an expected final step to the process; an expected response; a return value.</p>
<p>What is the return value of opening up the Twitter app? Browsing Facebook? Instagram? In fact, any social media?</p>
<p>The concrete answer is that there isn&rsquo;t one. Perhaps in those of us with resilient self-discipline, there may at least be some sort of time limitation. That&rsquo;s the most we can hope for, however, and no wonder - that&rsquo;s what these and other similar services have been <em>designed</em> for. They&rsquo;re built to be open-ended black-holes for our most precious resource&hellip; time.</p>
<blockquote>
<p>In the case of the Analytical Engine we have undoubtedly to lay out a certain capital of analytical labour in one particular line; but this is in order that the engine may bring us in a much larger return in another line.</p>
<p><em>Ada Augusta (Ada Lovelace)</em> - <a href="https://www.fourmilab.ch/babbage/sketch.html">Notes on <em>Sketch of The Analytical Engine</em></a></p>
</blockquote>
<p>Okay, so I did some more reading. Specifically, #ThrowbackThursday to the mid 1800&rsquo;s and something my good friend Ada Lovelace once scribbled in a book. Widely considered one of the first computer programmers, she and Charles Babbage pioneered many concepts that programmers today take for granted. The one I&rsquo;m going to hang my point on is, I think, nicely encapsulated in the above quote: the things programmers make are supposed to save you time.</p>
<p>Save it. Not lose it.</p>
<p>I think Ada and Charles would agree that, observing the effects of social media apps, clickbait news sites, and many other forms of attention-hogging interactivity that we haven&rsquo;t even classified yet - something&rsquo;s gone horribly wrong.</p>
<p>What if, as programmers, we actually did something about it?</p>
<p>Consider that collectively - no, even individually - we who design and build the workings of modern technology have an <em>incredible</em> amount of power. The next indie app that goes viral on ProductHunt will consume hundreds of hours of time from its users. Where is all that untapped, pure potential going to? Some open-ended, inoffensive amusement? Another advertising platform thinly veiled as a game? Perhaps another drop of oil to smooth the machinery of The Great Engine of Commerce?</p>
<p>I get it - programmers will build what they&rsquo;re paid to build. That&rsquo;s capitalism, that&rsquo;s feeding your family, survival&ndash;life. I&rsquo;m not trying to suggest we all quit our jobs, go live in the woods, and volunteer as humanitarians. That would be nice, but it&rsquo;s impractical.</p>
<p>But we all have side projects. Free time. What are you doing with yours?</p>
<hr>
<p>Before I&rsquo;m accused of being too hand-wavy and idealistic, I want to offer a concrete suggestion. Build things that save time. Not in the &ldquo;I&rsquo;ve made yet another to-do list app for you to download,&rdquo; kind of way, but in the &ldquo;Here&rsquo;s a one-liner to automate this mundane thing that would have taken you hours,&rdquo; kind of way. Here, have a <a href="/blog/batch-renaming-images-including-image-resolution-with-awk/">shameless plug</a>.</p>
<p>I also really like this idea from the first article I mentioned, so hang on tight while I bring this full circle:</p>
<blockquote>
<p><strong>What’s one concrete thing companies could do now to stop subverting our attention?</strong></p>
<p>I would just like to know what is the ultimate design goal of that site or that system that’s shaping my behavior or thinking. What are they really designing my experience for? Companies will say that their goal is to make the world open and connected or whatever. These are lofty marketing claims. But if you were to actually look at the dashboards that they’re designing, the high-level metrics they’re designing for, you probably wouldn’t see those things. You’d see other things, like frequency of use, time on site, this type of thing. If there was some way for the app to say, to the user, “Here’s generally what this app wants from you, from an attentional point of view,” that would be huge. It would probably be the primary way I would decide which apps I download and use.</p>
</blockquote>
<p>There are so many ways I&rsquo;d love to see this put into practice, from the obvious to the subversive. A little <code>position: sticky;</code> banner? A custom meta tag in the header? Maybe a call to action like this takes more introspection and honesty than a lot of app makers are ready for&hellip; but maybe it just takes a little of our time.</p>
]]></content></entry><entry><title type="html">Batch renaming images, including image resolution, with awk</title><link href="https://victoria.dev/archive/batch-renaming-images-including-image-resolution-with-awk/"/><id>https://victoria.dev/archive/batch-renaming-images-including-image-resolution-with-awk/</id><author><name>Victoria Drake</name></author><published>2017-11-20T13:59:30-05:00</published><updated>2017-11-20T13:59:30-05:00</updated><content type="html"><![CDATA[<p>The most recent item on my list of &ldquo;Geeky things I did that made me feel pretty awesome&rdquo; is an hour&rsquo;s adventure that culminated in this code:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ file IMG* | awk <span style="color:#e6db74">&#39;BEGIN{a=0} {print substr($1, 1, length($1)-5),a++&#34;_&#34;substr($8,1, length($8)-1)}&#39;</span> | <span style="color:#66d9ef">while</span> read fn fr; <span style="color:#66d9ef">do</span> echo <span style="color:#66d9ef">$(</span>rename -v <span style="color:#e6db74">&#34;s/</span>$fn<span style="color:#e6db74">/img_</span>$fr<span style="color:#e6db74">/g&#34;</span> *<span style="color:#66d9ef">)</span>; <span style="color:#66d9ef">done</span>
</span></span><span style="display:flex;"><span>IMG_20170808_172653_425.jpg renamed as img_0_4032x3024.jpg
</span></span><span style="display:flex;"><span>IMG_20170808_173020_267.jpg renamed as img_1_3024x3506.jpg
</span></span><span style="display:flex;"><span>IMG_20170808_173130_616.jpg renamed as img_2_3024x3779.jpg
</span></span><span style="display:flex;"><span>IMG_20170808_173221_425.jpg renamed as img_3_3024x3780.jpg
</span></span><span style="display:flex;"><span>IMG_20170808_173417_059.jpg renamed as img_4_2956x2980.jpg
</span></span><span style="display:flex;"><span>IMG_20170808_173450_971.jpg renamed as img_5_3024x3024.jpg
</span></span><span style="display:flex;"><span>IMG_20170808_173536_034.jpg renamed as img_6_4032x3024.jpg
</span></span><span style="display:flex;"><span>IMG_20170808_173602_732.jpg renamed as img_7_1617x1617.jpg
</span></span><span style="display:flex;"><span>IMG_20170808_173645_339.jpg renamed as img_8_3024x3780.jpg
</span></span><span style="display:flex;"><span>IMG_20170909_170146_585.jpg renamed as img_9_3036x3036.jpg
</span></span><span style="display:flex;"><span>IMG_20170911_211522_543.jpg renamed as img_10_3036x3036.jpg
</span></span><span style="display:flex;"><span>IMG_20170913_071608_288.jpg renamed as img_11_2760x2760.jpg
</span></span><span style="display:flex;"><span>IMG_20170913_073205_522.jpg renamed as img_12_2738x2738.jpg
</span></span><span style="display:flex;"><span>// ... etc etc
</span></span></code></pre></div><p>The last item on the aforementioned list is &ldquo;TODO: come up with a shorter title for this list.&rdquo;</p>
<p>I previously wrote about the power of command line tools like <a href="/posts/how-to-replace-a-string-in-a-dozen-old-blog-posts-with-one-sed-terminal-command/">sed</a>. This post expands on how to string all this magical functionality into one big, long, rainbow-coloured, viscous stream of awesome.</p>
<h2 id="rename-files">Rename files</h2>
<p>The tool that actually handles the renaming of our files is, appropriately enough, <code>rename</code>. The syntax is: <code>rename -n &quot;s/original_filename/new_filename/g&quot; *</code> where <code>-n</code> does a dry-run, and substituting <code>-v</code> would rename the files. The <code>s</code> indicates our substitution string, and <code>g</code> for &ldquo;global&rdquo; finds all occurrences of the string. The <code>*</code> matches zero or more occurrences of our search-and-replace parameters.</p>
<p>We&rsquo;ll come back to this later.</p>
<h2 id="get-file-information">Get file information</h2>
<p>When I run <code>$ file IMG_20170808_172653_425.jpg</code> in the image directory, I get this output:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>IMG_20170808_172653_425.jpg: JPEG image data, baseline, precision 8, 4032x3024, frames <span style="color:#ae81ff">3</span>
</span></span></code></pre></div><p>Since we can get the image resolution (&ldquo;4032x3024&rdquo; above), we know that we&rsquo;ll be able to use it in our new filename.</p>
<h2 id="isolate-the-information-we-want">Isolate the information we want</h2>
<p>I love <code>awk</code> for its simplicity. It takes lines of text and makes individual bits of information available to us with built in variables that we can then refer to as column numbers denoted by <code>$1</code>, <code>$2</code>, etc. By default, <code>awk</code> splits up columns on whitespace. To take the example above:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>|              1               |   2  |   3   |   4   |     5     |     6     | 7  |      8     |   9    | 10 |
</span></span><span style="display:flex;"><span>-------------------------------------------------------------------------------------------------------------
</span></span><span style="display:flex;"><span>| IMG_20170808_172653_425.jpg: | JPEG | image | data, | baseline, | precision | 8, | 4032x3024, | frames | 3  |
</span></span></code></pre></div><p>We can denote different values to use as a splitter with, for example, <code>-F','</code> if we wanted to use commas as the column divisions. For our current project, spaces are fine.</p>
<p>There are a couple issues we need to solve before we can plug the information into our new filenames. Column <code>$1</code> has the original filename we want, but there&rsquo;s an extra &ldquo;:&rdquo; character on the end. We don&rsquo;t need the &ldquo;.jpg&rdquo; either. Column <code>$8</code> has an extra &ldquo;,&rdquo; that we don&rsquo;t want as well. To get just to information we need, we&rsquo;ll take a substring of the column with <code>substr()</code>:</p>
<p><code>substr($1, 1, length($1)-5)</code> - This gives us the file name from the beginning of the string to the end of the string, minus 5 characters (&ldquo;length minus 5&rdquo;).
<code>substr($8,1, length($8)-1)</code> - This gives us the image size, without the extra comma (&ldquo;length minus 1&rdquo;).</p>
<h2 id="avoid-duplicate-file-names">Avoid duplicate file names</h2>
<p>To ensure that two images with the same resolutions don&rsquo;t create identical, competing file names, we&rsquo;ll append a unique incrementing number to the filename.</p>
<p><code>BEGIN{a=0}</code> - Using <code>BEGIN</code> tells <code>awk</code> to run the following code only once, at the (drumroll) beginning. Here, we&rsquo;re declaring the variable <code>a</code> to be <code>0</code>.
<code>a++</code> - Later in our code, at the appropriate spot for our file name, we call <code>a</code> and increment it.</p>
<p>When <code>awk</code> prints a string, it concatenates everything that isn&rsquo;t separated by a comma. <code>{print a b c}</code> would create &ldquo;abc&rdquo; and <code>{print a,b,c}</code> would create &ldquo;a b c&rdquo;, for example.</p>
<p>We can add additional characters to our file name, such as an underscore, by inserting it in quotations: <code>&quot;_&quot;</code>.</p>
<h2 id="string-it-all-together">String it all together</h2>
<p>To feed the output of one command into another command, we use &ldquo;pipe,&rdquo; written as <code>|</code>.</p>
<p>If we only used pipe in this instance, all our data from <code>file</code> and <code>awk</code> would get fed into <code>rename</code> all at once, making for one very, very long and probably non-compiling file name. To run the <code>rename</code> command line by line, we can use <code>while</code> and <code>read</code>. Similarly to <code>awk</code>, <code>read</code> takes input and splits it into variables we can assign and use. In our code, it takes the first bit of output from <code>awk</code> (the original file name) and assigns that the variable name <code>$fn</code>. It takes the second output (our incrementing number and the image resolution) and assigns that to <code>$fr</code>. The variable names are arbitrary; you can call them whatever you want.</p>
<p>To run our <code>rename</code> commands as if we&rsquo;d manually entered them in the terminal one by one, we can use <code>echo $(some command)</code>. Finally, <code>done</code> ends our <code>while</code> loop.</p>
<h2 id="bonus-round-rainbow-output">Bonus round: rainbow output</h2>
<p>I wasn&rsquo;t kidding with that <a href="https://github.com/tehmaze/lolcat">&ldquo;rainbow-coloured&rdquo; bit&hellip;</a></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>p install lolcat
</span></span></code></pre></div><p>Here&rsquo;s our full code:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>le IMG* | awk <span style="color:#e6db74">&#39;BEGIN{a=0} {print substr($1, 1, length($1)-5),a++&#34;_&#34;substr($8,1, length($8)-1)}&#39;</span> | <span style="color:#66d9ef">while</span> read fn fs; <span style="color:#66d9ef">do</span> echo <span style="color:#66d9ef">$(</span>rename -v <span style="color:#e6db74">&#34;s/</span>$fn<span style="color:#e6db74">/img_</span>$fs<span style="color:#e6db74">/g&#34;</span> *<span style="color:#66d9ef">)</span>; <span style="color:#66d9ef">done</span> | lolcat
</span></span></code></pre></div><p>Enjoy!</p>
]]></content></entry><entry><title type="html">How to code a satellite algorithm and cook paella from scratch</title><link href="https://victoria.dev/archive/how-to-code-a-satellite-algorithm-and-cook-paella-from-scratch/"/><id>https://victoria.dev/archive/how-to-code-a-satellite-algorithm-and-cook-paella-from-scratch/</id><author><name>Victoria Drake</name></author><published>2017-09-08T16:50:24-04:00</published><updated>2017-09-08T16:50:24-04:00</updated><content type="html"><![CDATA[<p>What if I told you that by the end of this article, you&rsquo;ll be able to calculate the orbital period of satellites around Earth using their average altitudes and&hellip; You tuned out already, didn&rsquo;t you?</p>
<p>Okay, how about this: I&rsquo;m going to teach you how to make paella!</p>
<p><em>And</em> you&rsquo;ll have written a function that does <em>the stuff I mentioned above</em>, just like I did for a freeCodeCamp challenge.</p>
<p>I promise there&rsquo;s an overarching moral lesson that will benefit you every day for the rest of your life. Or at least, feed you for one night. Let&rsquo;s get started.</p>
<h2 id="the-only-thing-i-know-about-paella-is-that-its-an-emoticon">The only thing I know about paella is that it&rsquo;s an emoticon</h2>
<figure><img src="/archive/how-to-code-a-satellite-algorithm-and-cook-paella-from-scratch/solve-unicode-paella.jpg"
    alt="Unicode paella emoji."><figcaption>
      <p>Unless you&rsquo;re reading this on a Samsung phone, in which case you&rsquo;re looking at a Korean hotpot.</p>
    </figcaption>
</figure>

<p>One of my favorite things about living in the world today is that it&rsquo;s <em>totally fine</em> to know next-to-nothing about something. A hundred years ago you might have gone your whole life not knowing anything more about paella other than that it&rsquo;s an emoticon.* But today? You can simply <a href="https://en.wikipedia.org/wiki/Paella">look it up</a>.</p>
<p>*That was a joke.</p>
<p>As with all things in life, when we are unsure, we turn to the internet - in this case, the entry for <em>paella</em> on Wikipedia, which reads:</p>
<blockquote>
<p>Paella &hellip;is a Valencian rice dish. Paella has ancient roots, but its modern form originated in the mid-19th century near the Albufera lagoon on the east coast of Spain adjacent to the city of Valencia. Many non-Spaniards view paella as Spain&rsquo;s national dish, but most Spaniards consider it to be a regional Valencian dish. Valencians, in turn, regard paella as one of their identifying symbols.</p>
</blockquote>
<p>At this point, you&rsquo;re probably full of questions. Do I need to talk to a Valencian? Should I take an online course on the history of Spain? What type of paella should I try to make? What is the common opinion of modern chefs when it comes to paella types?</p>
<p>If you set out with the intention of answering all these questions, one thing is certain: you&rsquo;ll never end up actually making paella. You&rsquo;ll spend hours upon hours typing questions into search engines and years later wake up with a Masters in Valencian Cuisine.</p>
<h2 id="the-most-important-question-method">The &ldquo;Most Important Question&rdquo; method</h2>
<p>When I talk to myself out loud in public (doesn&rsquo;t everyone?) I refer to this as &ldquo;MIQ&rdquo; (rhymes with &ldquo;Nick&rdquo;). I also imagine MIQ to be a rather crunchy and quite adorable anthropomorphized tortilla chip. Couldn&rsquo;t tell you why.</p>
<p><img src="solve-miq.png#center" alt="MIQ the chip."></p>
<p>MIQ swings his crunchy triangular body around to point me in the right direction, and the right direction always takes the form of the most important question that you need to ask yourself at any stage of problem solving. The first most important question is always this:</p>
<p><strong>What is the scope of the objective I want to achieve?</strong></p>
<p>Well, you want to make paella.</p>
<p>The next MIQ then becomes: how much do I actually need to know about paella in order to start making it?</p>
<p>You&rsquo;ve heard this advice before: any big problem can be broken down into multiple, but more manageable, bite-size problems. In this little constellation of bite-size problems, there&rsquo;s only <em>one</em> that you need to solve in order to get <em>most of the way</em> to a complete solution.</p>
<p>In the case of making paella, we need a recipe. That&rsquo;s a bite-size problem that a search engine can solve for us:</p>
<blockquote>
<p><strong>Simple Paella Recipe</strong></p>
<ol>
<li>In a medium bowl, mix together 2 tablespoons olive oil, paprika, oregano, and salt and pepper. Stir in chicken pieces to coat. Cover, and refrigerate.</li>
<li>Heat 2 tablespoons olive oil in a large skillet or paella pan over medium heat. Stir in garlic, red pepper flakes, and rice. Cook, stirring, to coat rice with oil, about 3 minutes. Stir in saffron threads, bay leaf, parsley, chicken stock, and lemon zest. Bring to a boil, cover, and reduce heat to medium low. Simmer 20 minutes.</li>
<li>Meanwhile, heat 2 tablespoons olive oil in a separate skillet over medium heat. Stir in marinated chicken and onion; cook 5 minutes. Stir in bell pepper and sausage; cook 5 minutes. Stir in shrimp; cook, turning the shrimp, until both sides are pink.</li>
<li>Spread rice mixture onto a serving tray. Top with meat and seafood mixture. (<a href="https://www.allrecipes.com/recipe/84137/easy-paella/">allrecipes.com</a>)</li>
</ol>
</blockquote>
<p>And <em>voila</em>! Believe it or not, we&rsquo;re <em>most of the way</em> there already.</p>
<p>Having a set of step-by-step instructions that are easy to understand is really most of the work done. All that&rsquo;s left is to go through the motions of gathering the ingredients and then making paella. From this point on, your MIQs may become fewer and far between, and they may slowly decrease in importance in relation to the overall problem. (Where do I buy paprika? How do I know when sausage is cooked? How do I set the timer on my phone for 20 minutes? How do I stop thinking about this delicious smell? Which Instagram filter best captures the ecstasy of this paella right now?)</p>
<figure><img src="/archive/how-to-code-a-satellite-algorithm-and-cook-paella-from-scratch/solve-insta-paella.jpg"
    alt="The answer to that last one is Nashville"><figcaption>
      <p>The answer to that last one is Nashville</p>
    </figcaption>
</figure>

<h2 id="i-still-know-nothing-about-calculating-the-orbital-periods-of-satellites">I still know nothing about calculating the orbital periods of satellites</h2>
<p>Okay. Let&rsquo;s examine the problem:</p>
<blockquote>
<p>Return a new array that transforms the element&rsquo;s average altitude into their orbital periods.</p>
<p>The array will contain objects in the format {name: &rsquo;name&rsquo;, avgAlt: avgAlt}.</p>
<p>You can read about orbital periods on wikipedia.</p>
<p>The values should be rounded to the nearest whole number. The body being orbited is Earth.</p>
<p>The radius of the earth is 6367.4447 kilometers, and the GM value of earth is 398600.4418 km3s-2.</p>
<p><code>orbitalPeriod([{name : &quot;sputnik&quot;, avgAlt : 35873.5553}])</code> should return <code>[{name: &quot;sputnik&quot;, orbitalPeriod: 86400}].</code></p>
</blockquote>
<p>Well, as it turns out, in order to calculate the orbital period of satellites, we also need a recipe. Amazing, the things you can find on the internet these days.</p>
<p>Courtesy of <a href="http://www.dummies.com/education/science/physics/how-to-calculate-the-period-and-orbiting-radius-of-a-geosynchronous-satellite/">dummies.com</a> (yup! #noshame), here&rsquo;s our recipe:</p>
<figure><img src="/archive/how-to-code-a-satellite-algorithm-and-cook-paella-from-scratch/solve-orbital-period.png"
    alt="Orbital period formula"><figcaption>
      <p>It&rsquo;s kind of cute, in a way.</p>
    </figcaption>
</figure>

<p>That might look pretty complicated, but as we&rsquo;ve already seen, we just need to answer the next MIQ: how much do I actually need to know about this formula in order to start using it?</p>
<p>In the case of this challenge, not too much. We&rsquo;re already given <code>earthRadius</code>, and <code>avgAlt</code> is part of our arguments object. Together, they form the radius, <em>r</em>. With a couple search queries and some mental time-travel to your elementary math class, we can describe this formula in a smattering of English:</p>
<p><strong><em>T</em>, the orbital period, equals 2 multiplied by Pi, in turn multiplied by the square root of the radius, <em>r</em> cubed, divided by the gravitational mass, <em>GM</em>.</strong></p>
<p>JavaScript has a <code>Math.PI</code> property, as well as <code>Math.sqrt()</code> function and <code>Math.pow()</code> function. Using those combined with simple calculation, we can represent this equation in a single line assigned to a variable:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">orbitalPeriod</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">2</span> <span style="color:#f92672">*</span> Math.<span style="color:#a6e22e">PI</span> <span style="color:#f92672">*</span> (Math.<span style="color:#a6e22e">sqrt</span>(Math.<span style="color:#a6e22e">pow</span>((<span style="color:#a6e22e">earthRadius</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">avgAlt</span>), <span style="color:#ae81ff">3</span>) <span style="color:#f92672">/</span> <span style="color:#a6e22e">GM</span>));
</span></span></code></pre></div><p>From the inside out:</p>
<ol>
<li>Add <code>earthRadius</code> and <code>avgAlt</code></li>
<li>Cube the result of step 1</li>
<li>Divide the result of step 2 by GM</li>
<li>Take the square root of the result of step 3</li>
<li>Multiply 2 times Pi times the result of step 4</li>
<li>Assign the returned value to <code>orbitalPeriod</code></li>
</ol>
<p>Believe it or not, we&rsquo;re already most of the way there.</p>
<p>The next MIQ for this challenge is to take the arguments object, extract the information we need, and return the result of our equation in the required format. There are a multitude of ways to do this, but I&rsquo;m happy with a straightforward <code>for</code> loop:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">orbitalPeriod</span>(<span style="color:#a6e22e">arr</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">resultArr</span> <span style="color:#f92672">=</span> [];
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">for</span> (<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">teapot</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">teapot</span> <span style="color:#f92672">&lt;</span> <span style="color:#a6e22e">arguments</span>[<span style="color:#ae81ff">0</span>].<span style="color:#a6e22e">length</span>; <span style="color:#a6e22e">teapot</span><span style="color:#f92672">++</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">GM</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">398600.4418</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">earthRadius</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">6367.4447</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">avgAlt</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">arguments</span>[<span style="color:#ae81ff">0</span>][<span style="color:#a6e22e">teapot</span>][<span style="color:#e6db74">&#39;avgAlt&#39;</span>];
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">name</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">arguments</span>[<span style="color:#ae81ff">0</span>][<span style="color:#a6e22e">teapot</span>][<span style="color:#e6db74">&#39;name&#39;</span>];
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">orbitalPeriod</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">2</span> <span style="color:#f92672">*</span> Math.<span style="color:#a6e22e">PI</span> <span style="color:#f92672">*</span> (Math.<span style="color:#a6e22e">sqrt</span>(Math.<span style="color:#a6e22e">pow</span>((<span style="color:#a6e22e">earthRadius</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">avgAlt</span>), <span style="color:#ae81ff">3</span>) <span style="color:#f92672">/</span> <span style="color:#a6e22e">GM</span>));
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">result</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">name</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">name</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">orbitalPeriod</span><span style="color:#f92672">:</span> Math.<span style="color:#a6e22e">round</span>(<span style="color:#a6e22e">orbitalPeriod</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">resultArr</span>.<span style="color:#a6e22e">push</span>(<span style="color:#a6e22e">result</span>);
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">resultArr</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>If you need a refresher on iterating through arrays, have a look at my <a href="/blog/iterating-over-objects-and-arrays-frequent-errors/">article on iterating, featuring breakfast arrays</a>! (5 minutes read)</p>
<p>Don&rsquo;t look now, but you just gained the ability to calculate the orbital period of satellites. You could even do it <em>while</em> making paella, if you wanted to. Seriously. Put it on your resume.</p>
<h2 id="tldr-the-overarching-moral-lesson">Tl;dr: the overarching moral lesson</h2>
<p>Whether it&rsquo;s cooking, coding, or anything else, problems may at first seem confusing, insurmountable, or downright boring. If you&rsquo;re faced with such a challenge, just remember: they&rsquo;re a lot more digestible with a side of bite-sized MIQ chips.</p>
<p><img src="solve-miq-bowl.png#center" alt="Bowl of MIQs."></p>
]]></content></entry><entry><title type="html">Making sandwiches with closures in JavaScript</title><link href="https://victoria.dev/archive/making-sandwiches-with-closures-in-javascript/"/><id>https://victoria.dev/archive/making-sandwiches-with-closures-in-javascript/</id><author><name>Victoria Drake</name></author><published>2017-05-28T09:16:35+07:00</published><updated>2017-05-28T09:16:35+07:00</updated><content type="html"><![CDATA[<p>Say you&rsquo;re having a little coding get-together, and you need some sandwiches. You happen to know that everyone prefers a different type of sandwich, like chicken, ham, or peanut butter and mayo. You could make all these sandwiches yourself, but that would be tedious and boring.</p>
<p>Luckily, you know of a nearby sandwich shop that delivers. They have the ability and ingredients to make any kind of sandwich in the world, and all you have to do is order through their app.</p>
<p>The sandwich shop looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">makeMeASandwich</span>(<span style="color:#a6e22e">x</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">ingredients</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">x</span>.<span style="color:#a6e22e">join</span>(<span style="color:#e6db74">&#39; &#39;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">barry</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">ingredients</span>.<span style="color:#a6e22e">concat</span>(<span style="color:#e6db74">&#39; sandwich&#39;</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Notice that we have an outer function, <code>makeMeASandwich()</code> that takes an argument, <code>x</code>. This outer function has the local variable <code>ingredients</code>, which is just <code>x</code> mushed together.</p>
<p>Barry? Who&rsquo;s Barry? He&rsquo;s the guy who works at the sandwich shop. You&rsquo;ll never talk with Barry directly, but he&rsquo;s the reason your sandwiches are made, and why they&rsquo;re so delicious. Barry takes <code>ingredients</code> and mushes them together with &quot; sandwich&quot;.</p>
<p>The reason Barry is able to access the <code>ingredients</code> is because they&rsquo;re in his outer scope. If you were to take Barry out of the sandwich shop, he&rsquo;d no longer be able to access them. This is an example of <em>lexical scoping</em>: &ldquo;Nested functions have access to variables declared in their outer scope.&rdquo; (<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures#Lexical_scoping">MDN</a>)</p>
<p>Barry, happily at work in the sandwich shop, is an example of a closure.</p>
<blockquote>
<p><strong>Closures</strong> are functions that refer to independent (free) variables (variables that are used locally, but defined in an enclosing scope). In other words, these functions &lsquo;remember&rsquo; the environment in which they were created. (<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures">MDN</a>)</p>
</blockquote>
<p>When you order, the app submits your sandwich request like so:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">pbm</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">makeMeASandwich</span>([<span style="color:#e6db74">&#39;peanut butter&#39;</span>, <span style="color:#e6db74">&#39;mayo&#39;</span>]);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">pbm</span>();
</span></span></code></pre></div><p>And in thirty-minutes-or-it&rsquo;s-free, you get: <code>peanut butter mayo sandwich</code>.</p>
<p>The nice thing about the sandwich shop app is that it remembers the sandwiches you&rsquo;ve ordered before. Your peanut butter and mayo sandwich is now available to you as <code>pbm()</code> for you to order anytime. It&rsquo;s pretty convenient since, each time you order, there&rsquo;s no need to specify that the sandwich you want is the same one you got before with peanut butter and mayo and it&rsquo;s a sandwich. Using <code>pbm()</code> is much more concise.</p>
<p>Let&rsquo;s order the sandwiches you need for the party:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">pmrp</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">makeMeASandwich</span>([<span style="color:#e6db74">&#39;prosciutto&#39;</span>, <span style="color:#e6db74">&#39;mozzarella&#39;</span>, <span style="color:#e6db74">&#39;red pepper&#39;</span>]);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">pbt</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">makeMeASandwich</span>([<span style="color:#e6db74">&#39;peanut butter&#39;</span>, <span style="color:#e6db74">&#39;tuna&#39;</span>]);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">hm</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">makeMeASandwich</span>([<span style="color:#e6db74">&#39;ham&#39;</span>]);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">pbm</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">makeMeASandwich</span>([<span style="color:#e6db74">&#39;peanut butter&#39;</span>, <span style="color:#e6db74">&#39;mayo&#39;</span>]);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">pmrp</span>();
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">pbt</span>();
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">hm</span>();
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">pbm</span>();
</span></span></code></pre></div><p>Your order confirmation reads:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>prosciutto mozzarella red pepper sandwich
</span></span><span style="display:flex;"><span>peanut butter tuna sandwich
</span></span><span style="display:flex;"><span>ham sandwich
</span></span><span style="display:flex;"><span>peanut butter mayo sandwich
</span></span></code></pre></div><p>Plot twist! The guy who wanted a ham sandwich now wants a ham <em>and cheese</em> sandwich. Luckily, the sandwich shop just released a new version of their app that will let you add cheese to any sandwich.</p>
<p>With this added feature, the sandwich shop now looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">makeMeASandwich</span>(<span style="color:#a6e22e">x</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">ingredients</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">x</span>.<span style="color:#a6e22e">join</span>(<span style="color:#e6db74">&#39; &#39;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">slices</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">barry</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">ingredients</span>.<span style="color:#a6e22e">concat</span>(<span style="color:#e6db74">&#39; sandwich&#39;</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">barryAddCheese</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">slices</span> <span style="color:#f92672">+=</span> <span style="color:#ae81ff">2</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">ingredients</span>.<span style="color:#a6e22e">concat</span>(<span style="color:#e6db74">&#39; sandwich with &#39;</span>, <span style="color:#a6e22e">slices</span>, <span style="color:#e6db74">&#39; slices of cheese&#39;</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">noCheese</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">function</span>() {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">barry</span>();
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">addCheese</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">function</span>() {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">barryAddCheese</span>();
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>You amend the order to look like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#a6e22e">pmrp</span>.<span style="color:#a6e22e">noCheese</span>();
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">pbt</span>.<span style="color:#a6e22e">noCheese</span>();
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">hm</span>.<span style="color:#a6e22e">addCheese</span>();
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">pbm</span>.<span style="color:#a6e22e">noCheese</span>();
</span></span></code></pre></div><p>And your order confirmation reads:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>prosciutto mozzarella red pepper sandwich
</span></span><span style="display:flex;"><span>peanut butter tuna sandwich
</span></span><span style="display:flex;"><span>ham sandwich with <span style="color:#ae81ff">2</span> slices of cheese
</span></span><span style="display:flex;"><span>peanut butter mayo sandwich
</span></span></code></pre></div><p>You&rsquo;ll notice that when you order a sandwich with cheese, Barry puts 2 slices of cheese on it. In this way, the sandwich shop controls how much cheese you get. You can&rsquo;t get to Barry to tell him you want more than 2 slices at a time. That&rsquo;s because your only access to the sandwich shop is through the public functions <code>noCheese</code> or <code>addCheese</code>.</p>
<p>Of course, there&rsquo;s a way to cheat the system&hellip;</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#a6e22e">hm</span>.<span style="color:#a6e22e">addCheese</span>();
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">hm</span>.<span style="color:#a6e22e">addCheese</span>();
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">hm</span>.<span style="color:#a6e22e">addCheese</span>();
</span></span></code></pre></div><p>By ordering the same ham sandwich with cheese three times, you get: <code>ham sandwich with 6 slices of cheese</code>.</p>
<p>This happens because the sandwich shop app recognizes the variable <code>hm</code> as the same sandwich each time, and increases the number of cheese slices it tells Barry to add.</p>
<p>The app could prevent you from adding lots of cheese to the same sandwich, either by adding a maximum or by appending unique order numbers to the variable names&hellip; but this is our fantasy sandwich shop, and we get to pile on as much cheese as we want.</p>
<p><img src="closures-cheesestack.jpg#center" alt="All the cheese."></p>
<p>By using closures, we can have JavaScript emulate private methods found in languages like Ruby and Java. Closures are a useful way to extend the functionality of JavaScript, and also order sandwiches.</p>
]]></content></entry><entry><title type="html">Understanding Array.prototype.reduce() and recursion using apple pie</title><link href="https://victoria.dev/archive/understanding-array.prototype.reduce-and-recursion-using-apple-pie/"/><id>https://victoria.dev/archive/understanding-array.prototype.reduce-and-recursion-using-apple-pie/</id><author><name>Victoria Drake</name></author><published>2017-05-18T11:40:06+07:00</published><updated>2017-05-18T11:40:06+07:00</updated><content type="html"><![CDATA[<p>I was having trouble understanding <code>reduce()</code> and recursion in JavaScript, so I wrote this article to explain it to myself (hey, look, recursion!). I hope you find my examples both helpful and delicious.</p>
<p>Given an array with nested arrays:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">arr</span> <span style="color:#f92672">=</span> [<span style="color:#ae81ff">1</span>, [<span style="color:#ae81ff">2</span>], [<span style="color:#ae81ff">3</span>, [[<span style="color:#ae81ff">4</span>]]]]
</span></span></code></pre></div><p>We want to produce this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">flat</span> <span style="color:#f92672">=</span> [<span style="color:#ae81ff">1</span>, <span style="color:#ae81ff">2</span>, <span style="color:#ae81ff">3</span>, <span style="color:#ae81ff">4</span>]
</span></span></code></pre></div><h2 id="using-for-loops-and-if-statements">Using for loops and if statements</h2>
<p>Naively, if we know the maximum number of nested arrays we&rsquo;ll encounter (there are 4 in this example), we can use <code>for</code> loops to iterate through each array item, then <code>if</code> statements to check if each item is in itself an array, and so on&hellip;</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">flatten</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">flat</span> <span style="color:#f92672">=</span> [];
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> (<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">i</span><span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">&lt;</span><span style="color:#a6e22e">arr</span>.<span style="color:#a6e22e">length</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (Array.<span style="color:#a6e22e">isArray</span>(<span style="color:#a6e22e">arr</span>[<span style="color:#a6e22e">i</span>])) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">for</span> (<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">ii</span><span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">ii</span><span style="color:#f92672">&lt;</span><span style="color:#a6e22e">arr</span>[<span style="color:#a6e22e">i</span>].<span style="color:#a6e22e">length</span>; <span style="color:#a6e22e">ii</span><span style="color:#f92672">++</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (Array.<span style="color:#a6e22e">isArray</span>(<span style="color:#a6e22e">arr</span>[<span style="color:#a6e22e">i</span>][<span style="color:#a6e22e">ii</span>])) {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">for</span> (<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">iii</span><span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">iii</span><span style="color:#f92672">&lt;</span><span style="color:#a6e22e">arr</span>[<span style="color:#a6e22e">i</span>][<span style="color:#a6e22e">ii</span>].<span style="color:#a6e22e">length</span>; <span style="color:#a6e22e">iii</span><span style="color:#f92672">++</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">for</span> (<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">iiii</span><span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">iiii</span><span style="color:#f92672">&lt;</span><span style="color:#a6e22e">arr</span>[<span style="color:#a6e22e">i</span>][<span style="color:#a6e22e">ii</span>][<span style="color:#a6e22e">iii</span>].<span style="color:#a6e22e">length</span>; <span style="color:#a6e22e">iiii</span><span style="color:#f92672">++</span>) {
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">if</span> (Array.<span style="color:#a6e22e">isArray</span>(<span style="color:#a6e22e">arr</span>[<span style="color:#a6e22e">i</span>][<span style="color:#a6e22e">ii</span>][<span style="color:#a6e22e">iii</span>])) {
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">flat</span>.<span style="color:#a6e22e">push</span>(<span style="color:#a6e22e">arr</span>[<span style="color:#a6e22e">i</span>][<span style="color:#a6e22e">ii</span>][<span style="color:#a6e22e">iii</span>][<span style="color:#a6e22e">iiii</span>]);
</span></span><span style="display:flex;"><span>                } <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">flat</span>.<span style="color:#a6e22e">push</span>(<span style="color:#a6e22e">arr</span>[<span style="color:#a6e22e">i</span>][<span style="color:#a6e22e">ii</span>][<span style="color:#a6e22e">iii</span>]);
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><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:#a6e22e">flat</span>.<span style="color:#a6e22e">push</span>(<span style="color:#a6e22e">arr</span>[<span style="color:#a6e22e">i</span>][<span style="color:#a6e22e">ii</span>]);
</span></span><span style="display:flex;"><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:#a6e22e">flat</span>.<span style="color:#a6e22e">push</span>(<span style="color:#a6e22e">arr</span>[<span style="color:#a6e22e">i</span>]);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// [1, 2, 3, 4]
</span></span></span></code></pre></div><p>&hellip;Which works, but of course looks ridiculous. Besides looking ridiculous, a) it only works if we know how many nested arrays we&rsquo;ll process, b) it&rsquo;s hard to read and harder to understand, and c) can you imagine having to debug this mess?! (Gee, I think there&rsquo;s an extra <code>i</code> somewhere.)</p>
<h2 id="using-reduce">Using reduce</h2>
<p>JavaScript has a couple methods we can use to make our code a little less ridiculous. One of these is <code>reduce()</code> and it looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">flat</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">arr</span>.<span style="color:#a6e22e">reduce</span>(<span style="color:#66d9ef">function</span>(<span style="color:#a6e22e">done</span>,<span style="color:#a6e22e">curr</span>){
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">done</span>.<span style="color:#a6e22e">concat</span>(<span style="color:#a6e22e">curr</span>);
</span></span><span style="display:flex;"><span>}, []);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// [ 1, 2, 3, [ [ 4 ] ] ]
</span></span></span></code></pre></div><p>It&rsquo;s a lot less code, but we haven&rsquo;t taken care of some of the nested arrays. Let&rsquo;s first walk through <code>reduce()</code> together and examine what it does to see how we&rsquo;ll correct this.</p>
<blockquote>
<p><strong>Array.prototype.reduce()</strong>
The reduce() method applies a function against an accumulator and each element in the array (from left to right) to reduce it to a single value. (<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce?v=example">MDN</a>)</p>
</blockquote>
<p>It&rsquo;s not quite as complicated as it seems. Let&rsquo;s think of <code>reduce()</code> as an out-of-work developer (AI took all the dev jobs) with an empty basket. We&rsquo;ll call him Adam. Adam&rsquo;s main function (ba-dum ching) is now to take apples from a pile, shine them up, and put them one-by-one into the basket. This basket of shiny apples is destined to become delicious apple pies. It&rsquo;s a very important job.</p>
<figure><img src="/archive/understanding-array.prototype.reduce-and-recursion-using-apple-pie/recursion-apple-formula.jpg"
    alt="Pile of apples &#43; Adam: apple pie"><figcaption>
      <p>Apples plus human effort equals pie. Not to be confused with apple-human-pie, which is less appetizing.</p>
    </figcaption>
</figure>

<p>In our above example, the pile of apples is our array, <code>arr</code>. Our basket is <code>done</code>, the accumulator. The initial value of <code>done</code> is an empty array, which we see as <code>[]</code> at the end of our reduce function. The apple that our out-of-work dev is currently shining, you guessed it, is <code>curr</code>. Once Adam processes the current apple, he places it into the basket (<code>.concat()</code>). When there are no more apples in the pile, he returns the basket of polished apples to us, and then probably goes home to his cat, or something.</p>
<h2 id="using-reduce-recursively-to-address-nested-arrays">Using reduce recursively to address nested arrays</h2>
<p>So that&rsquo;s all well and good, and now we have a basket of polished apples. But we still have some nested arrays to deal with. Going back to our analogy, let&rsquo;s say that some of the apples in the pile are in boxes. Within each box there could be more apples, and/or more boxes containing smaller, cuter apples.</p>
<figure><img src="/archive/understanding-array.prototype.reduce-and-recursion-using-apple-pie/recursion-nested-apples.jpg"
    alt="Box within a box within a box with apples"><figcaption>
      <p>Adorable, slightly skewed apples just want to be loved/eaten.</p>
    </figcaption>
</figure>

<p>Here&rsquo;s what we want our apple-processing-function/Adam to do:</p>
<ol>
<li>If the pile of apples is a pile of apples, take an apple from the pile.</li>
<li>If the apple is an apple, polish it, put it in the basket.</li>
<li>If the apple is a box, open the box. If the box contains an apple, go to step 2.</li>
<li>If the box contains another box, open this box, and go to step 3.</li>
<li>When the pile is no more, give us the basket of shiny apples.</li>
<li>If the pile of apples is not a pile of apples, give back whatever it is.</li>
</ol>
<p>A recursive reduce function that accomplishes this is:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">flatten</span>(<span style="color:#a6e22e">arr</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (Array.<span style="color:#a6e22e">isArray</span>(<span style="color:#a6e22e">arr</span>)) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">arr</span>.<span style="color:#a6e22e">reduce</span>(<span style="color:#66d9ef">function</span>(<span style="color:#a6e22e">done</span>,<span style="color:#a6e22e">curr</span>){
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">done</span>.<span style="color:#a6e22e">concat</span>(<span style="color:#a6e22e">flatten</span>(<span style="color:#a6e22e">curr</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">return</span> <span style="color:#a6e22e">arr</span>;
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// [ 1, 2, 3, 4 ]
</span></span></span></code></pre></div><p>Bear with me and I&rsquo;ll explain.</p>
<blockquote>
<p>An act of a function calling itself. Recursion is used to solve problems that contain smaller sub-problems. A recursive function can receive two inputs: a base case (ends recursion) or a recursive case (continues recursion). (<a href="https://developer.mozilla.org/en-US/docs/Glossary/Recursion">MDN</a>)</p>
</blockquote>
<p>If you examine our code above, you&rsquo;ll see that <code>flatten()</code> appears twice. The first time it appears, it tells Adam what to do with the pile of apples. The second time, it tells him what to do with the thing he&rsquo;s currently holding, providing instructions in the case it&rsquo;s an apple, and in the case it&rsquo;s not an apple. The thing to note is that these instructions are a <em>repeat of the original instructions we started with</em> - and that&rsquo;s recursion.</p>
<p>We&rsquo;ll break it down line-by-line for clarity:</p>
<ol>
<li><code>function flatten(arr) {</code> - we name our overall function and specify that it will take an argument, <code>arr</code>.</li>
<li><code>if (Array.isArray(arr)) {</code> - we examine the provided &ldquo;arrgument&rdquo; (I know, I&rsquo;m very funny) to determine if it is an array.</li>
<li><code>return arr.reduce(function(done,curr){</code> - if the previous line is true and the argument is an array, we want to reduce it. This is our recursive case. We&rsquo;ll apply the following function to each array item&hellip;</li>
<li><code>return done.concat(flatten(curr));</code> - an unexpected plot twist appears! The function we want to apply is the very function we&rsquo;re in. Colloquially: take it from the top.</li>
<li><code>}, []);</code> - we tell our reduce function to start with an empty accumulator (<code>done</code>), and wrap it up.</li>
<li><code>} else {</code> - this resolves our if statement at line 2. If the provided argument isn&rsquo;t an array&hellip;</li>
<li><code>return arr;</code> - return whatever the <code>arr</code> is. (Hopefully a cute apple.) This is our base case that breaks us out of recursion.</li>
<li><code>}</code> - end the else statement.</li>
<li><code>}</code> - end the overall function.</li>
</ol>
<p>And we&rsquo;re done! We&rsquo;ve gone from our 24 line, 4-layers-deep nested <code>for</code> loop solution to a much more concise, 9 line recursive reduce solution. Reduce and recursion can seem a little impenetrable at first, but they&rsquo;re valuable tools that will save you lots of future effort once you grasp them.</p>
<p>And don&rsquo;t worry about Adam, our out-of-work developer. He got so much press after being featured in this article that he opened up his very own AI-managed apple pie factory. He&rsquo;s very happy.</p>
<figure><img src="/archive/understanding-array.prototype.reduce-and-recursion-using-apple-pie/recursion-adams-apples.jpg"
    alt="Adam&#39;s apple pie factory, Adam&#39;s Apples"><figcaption>
      <p>+1 for you if you saw that one coming.</p>
    </figcaption>
</figure>

]]></content></entry><entry><title type="html">Iterating over objects and arrays: frequent errors</title><link href="https://victoria.dev/archive/iterating-over-objects-and-arrays-frequent-errors/"/><id>https://victoria.dev/archive/iterating-over-objects-and-arrays-frequent-errors/</id><author><name>Victoria Drake</name></author><published>2017-05-16T10:46:46+07:00</published><updated>2017-05-16T10:46:46+07:00</updated><content type="html"><![CDATA[<p>Here&rsquo;s <del>some complaining</del> a quick overview of some code that has confounded me more than once. I&rsquo;m told even very experienced developers encounter these situations regularly, so if you find yourself on your third cup of coffee scratching your head over why your code is doing exactly what you told it to do (and not what you <em>want</em> it to do), maybe this post can help you.</p>
<p>The example code is JavaScript, since that&rsquo;s what I&rsquo;ve been working in lately, but I believe the concepts to be pretty universal.</p>
<h2 id="quick-reference-for-equivalent-statements">Quick reference for equivalent statements</h2>
<table>
<thead>
<tr>
<th>This&hellip;</th>
<th>&hellip;is the same as this</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>i++;</code></td>
<td><code>i = i + 1;</code></td>
</tr>
<tr>
<td><code>i--;</code></td>
<td><code>i = i - 1;</code></td>
</tr>
<tr>
<td><code>apples += 5</code></td>
<td><code>apples = apples + 5;</code></td>
</tr>
<tr>
<td><code>apples -= 5</code></td>
<td><code>apples = apples - 5;</code></td>
</tr>
<tr>
<td><code>apples *= 5</code></td>
<td><code>apples = apples * 5;</code></td>
</tr>
<tr>
<td><code>apples /= 5</code></td>
<td><code>apples = apples / 5;</code></td>
</tr>
</tbody>
</table>
<h2 id="quick-reference-for-logical-statements">Quick reference for logical statements</h2>
<table>
<thead>
<tr>
<th>This&hellip;</th>
<th>&hellip;gives this</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>3 == '3'</code></td>
<td><code>true</code> (type converted)</td>
</tr>
<tr>
<td><code>3 === '3'</code></td>
<td><code>false</code> (type matters; integer is not a string)</td>
</tr>
<tr>
<td><code>3 != '3'</code></td>
<td><code>false</code> (type converted, 3: 3)</td>
</tr>
<tr>
<td><code>3 !== '3'</code></td>
<td><code>true</code> (type matters; integer is not a string)</td>
</tr>
<tr>
<td>||</td>
<td>logical &ldquo;or&rdquo;: either side evaluated</td>
</tr>
<tr>
<td><code>&amp;&amp;</code></td>
<td>logical &ldquo;and&rdquo;: both sides evaluated</td>
</tr>
</tbody>
</table>
<h2 id="objects">Objects</h2>
<p>Given a breakfast object that looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">breakfast</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#39;eggs&#39;</span><span style="color:#f92672">:</span> <span style="color:#ae81ff">2</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#39;waffles&#39;</span><span style="color:#f92672">:</span> <span style="color:#ae81ff">2</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#39;fruit&#39;</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#39;blueberries&#39;</span><span style="color:#f92672">:</span> <span style="color:#ae81ff">5</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#39;strawberries&#39;</span><span style="color:#f92672">:</span> <span style="color:#ae81ff">1</span>,
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#39;coffee&#39;</span><span style="color:#f92672">:</span> <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Or like this:</p>
<p><img src="cover.png#center" alt="Breakfast object."></p>
<h3 id="iterate-over-object-properties">Iterate over object properties</h3>
<p>We can iterate through each breakfast item using a for loop as follows:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">for</span> (<span style="color:#a6e22e">item</span> <span style="color:#66d9ef">in</span> <span style="color:#a6e22e">breakfast</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#e6db74">&#39;item: &#39;</span>, <span style="color:#a6e22e">item</span>);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>This produces:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>item: eggs
</span></span><span style="display:flex;"><span>item: waffles
</span></span><span style="display:flex;"><span>item: fruit
</span></span><span style="display:flex;"><span>item: coffee
</span></span></code></pre></div><h3 id="get-object-property-value">Get object property value</h3>
<p>We can access the value of the property or nested properties (in this example, the number of items) like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#e6db74">&#39;How many waffles? &#39;</span>, <span style="color:#a6e22e">breakfast</span>[<span style="color:#e6db74">&#39;waffles&#39;</span>])
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#e6db74">&#39;How many strawberries? &#39;</span>, <span style="color:#a6e22e">breakfast</span>[<span style="color:#e6db74">&#39;fruit&#39;</span>][<span style="color:#e6db74">&#39;strawberries&#39;</span>])
</span></span></code></pre></div><p>Or equivalent syntax:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#e6db74">&#39;How many waffles? &#39;</span>, <span style="color:#a6e22e">breakfast</span>.<span style="color:#a6e22e">waffles</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#e6db74">&#39;How many strawberries? &#39;</span>, <span style="color:#a6e22e">breakfast</span>.<span style="color:#a6e22e">fruit</span>.<span style="color:#a6e22e">strawberries</span>)
</span></span></code></pre></div><p>This produces:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>How many waffles?  <span style="color:#ae81ff">2</span>
</span></span><span style="display:flex;"><span>How many strawberries?  <span style="color:#ae81ff">1</span>
</span></span></code></pre></div><h3 id="get-object-property-from-the-value">Get object property from the value</h3>
<p>If instead I want to access the property via the value, for example, to find out which items are served in twos, I can do so by iterating like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">for</span> (<span style="color:#a6e22e">item</span> <span style="color:#66d9ef">in</span> <span style="color:#a6e22e">breakfast</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">breakfast</span>[<span style="color:#a6e22e">item</span>] <span style="color:#f92672">==</span> <span style="color:#ae81ff">2</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#e6db74">&#39;Two of: &#39;</span>, <span style="color:#a6e22e">item</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Which gives us:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>Two of:  eggs
</span></span><span style="display:flex;"><span>Two of:  waffles
</span></span></code></pre></div><h3 id="alter-nested-property-values">Alter nested property values</h3>
<p>Say I want to increase the number of fruits in breakfast, because sugar is bad for me and I like things that are bad for me. I can do that like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">fruits</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">breakfast</span>[<span style="color:#e6db74">&#39;fruit&#39;</span>];
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> (<span style="color:#a6e22e">f</span> <span style="color:#66d9ef">in</span> <span style="color:#a6e22e">fruits</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">fruits</span>[<span style="color:#a6e22e">f</span>] <span style="color:#f92672">+=</span> <span style="color:#ae81ff">1</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#a6e22e">fruits</span>);
</span></span></code></pre></div><p>Which gives us:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span><span style="color:#f92672">{</span> blueberries: 6, strawberries: <span style="color:#ae81ff">2</span> <span style="color:#f92672">}</span>
</span></span></code></pre></div><h2 id="arrays">Arrays</h2>
<p>Given an array of waffles that looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">wafflesIAte</span> <span style="color:#f92672">=</span> [ <span style="color:#ae81ff">1</span>, <span style="color:#ae81ff">3</span>, <span style="color:#ae81ff">2</span>, <span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">5</span>, <span style="color:#ae81ff">2</span>, <span style="color:#ae81ff">11</span> ];
</span></span></code></pre></div><p>Or like this:</p>
<p><img src="iteration-waffles.png" alt="Waffle array."></p>
<h3 id="iterate-through-array-items">Iterate through array items</h3>
<p>We can iterate through each item in the array using a for loop:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">for</span> (<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span> <span style="color:#f92672">&lt;</span> <span style="color:#a6e22e">wafflesIAte</span>.<span style="color:#a6e22e">length</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#e6db74">&#39;array index: &#39;</span>, <span style="color:#a6e22e">i</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#e6db74">&#39;item from array: &#39;</span>, <span style="color:#a6e22e">wafflesIAte</span>[<span style="color:#a6e22e">i</span>]);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>This produces:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>array index:  <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>item from array:  <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>array index:  <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>item from array:  <span style="color:#ae81ff">3</span>
</span></span><span style="display:flex;"><span>array index:  <span style="color:#ae81ff">2</span>
</span></span><span style="display:flex;"><span>item from array:  <span style="color:#ae81ff">2</span>
</span></span><span style="display:flex;"><span>array index:  <span style="color:#ae81ff">3</span>
</span></span><span style="display:flex;"><span>item from array:  <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>array index:  <span style="color:#ae81ff">4</span>
</span></span><span style="display:flex;"><span>item from array:  <span style="color:#ae81ff">5</span>
</span></span><span style="display:flex;"><span>array index:  <span style="color:#ae81ff">5</span>
</span></span><span style="display:flex;"><span>item from array:  <span style="color:#ae81ff">2</span>
</span></span><span style="display:flex;"><span>array index:  <span style="color:#ae81ff">6</span>
</span></span><span style="display:flex;"><span>item from array:  <span style="color:#ae81ff">11</span>
</span></span></code></pre></div><p>Some things to remember:
<code>i</code> in the above context is a placeholder; we could substitute anything we like (<code>x</code>, <code>n</code>, <code>underpants</code>, etc). It simply denotes each instance of the iteration.</p>
<p><code>i &lt; wafflesIAte.length</code> tells our for loop to continue as long as <code>i</code> is less than the array&rsquo;s length (in this case, 7).</p>
<p><code>i++</code> is equivalent to <code>i+1</code> and means we&rsquo;re incrementing through our array by one each time. We could also use <code>i+2</code> to proceed with every other item in the array, for example.</p>
<h3 id="access-array-item-by-index">Access array item by index</h3>
<p>We can specify an item in the array using the array index, written as <code>wafflesIAte[i]</code> where <code>i</code> is any index of the array. This gives the item at that location.</p>
<p>Array index always starts with <code>0</code>, which is accessed with <code>wafflesIAte[0]</code>. Using <code>wafflesIAte[1]</code> gives us the second item in the array, which is &ldquo;3&rdquo;.</p>
<h3 id="ways-to-get-mixed-up-over-arrays">Ways to get mixed up over arrays</h3>
<p>Remember that <code>wafflesIAte.length</code> and the index of the last item in the array are different. The former is 7, the latter is <code>6</code>.</p>
<p>When incrementing <code>i</code>, remember that <code>[i+1]</code> and <code>[i]+1</code> are different:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#e6db74">&#39;[i+1] gives next array index: &#39;</span>, <span style="color:#a6e22e">wafflesIAte</span>[<span style="color:#ae81ff">0</span><span style="color:#f92672">+</span><span style="color:#ae81ff">1</span>]);
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#e6db74">&#39;[i]+1 gives index value + 1: &#39;</span>, <span style="color:#a6e22e">wafflesIAte</span>[<span style="color:#ae81ff">0</span>]<span style="color:#f92672">+</span><span style="color:#ae81ff">1</span>);
</span></span></code></pre></div><p>Produces:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span><span style="color:#f92672">[</span>i+1<span style="color:#f92672">]</span> gives next array index:  <span style="color:#ae81ff">3</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">[</span>i<span style="color:#f92672">]</span>+1 gives index value + 1:  <span style="color:#ae81ff">2</span>
</span></span></code></pre></div><h2 id="practice-makes-better">Practice makes&hellip; better</h2>
<p>The more often you code and correct your errors, the better you&rsquo;ll remember it next time!</p>
<p>That&rsquo;s all for now. If you have a correction, best practice, or another common error for me to add, please let me know!</p>
]]></content></entry><entry><title type="html">How to Replace a String with sed in Current and Recursive Subdirectories</title><link href="https://victoria.dev/posts/how-to-replace-a-string-with-sed-in-current-and-recursive-subdirectories/"/><id>https://victoria.dev/posts/how-to-replace-a-string-with-sed-in-current-and-recursive-subdirectories/</id><author><name>Victoria Drake</name></author><published>2017-05-06T20:04:53+08:00</published><updated>2025-06-23T20:04:53+08:00</updated><content type="html"><![CDATA[<p>I’ve probably run some variation of “find and replace across multiple files” thousands of times in my career. It’s one of those operations that seems straightforward until you’re staring at a codebase with 500,000 lines spread across 2,000 files, and you need to rename a function that’s used everywhere. Get it wrong, and you’re looking at hours of manual cleanup—or worse, subtle bugs that only surface in production.</p>
<p>Here&rsquo;s the approach I use, why some methods work better than others, and some tips that can save you from that sinking feeling when you realize you just broke prod.</p>
<h2 id="current-directory-only">Current Directory Only</h2>
<p>You can use sed by itself to make changes to files in the current directory, ignoring subdirectories.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>.
</span></span><span style="display:flex;"><span>├── index.html        <span style="color:#75715e"># Change this file</span>
</span></span><span style="display:flex;"><span>└── blog
</span></span><span style="display:flex;"><span>    ├── list.html     <span style="color:#75715e"># Don&#39;t change</span>
</span></span><span style="display:flex;"><span>    └── single.html   <span style="color:#75715e"># these files</span>
</span></span></code></pre></div><p>To replace all occurrences of “foo” with “bar” in files within the current directory:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>sed -i -- <span style="color:#e6db74">&#39;s/foo/bar/g&#39;</span> *
</span></span></code></pre></div><p>Here’s what each component of the command does:</p>
<ul>
<li><code>-i</code> will change the original, and stands for “in-place.”</li>
<li><code>s</code> is for substitute, so we can find and replace.</li>
<li><code>foo</code> is the string we’ll be taking away,</li>
<li><code>bar</code> is the string we’ll use instead today.</li>
<li><code>g</code> as in “global” means “all occurrences, please.”</li>
<li><code>*</code> denotes all file types. (No more rhymes. What a tease.)</li>
</ul>
<p>You can limit the operation to one file type, such as Python files, by using a matching pattern:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>sed -i -- <span style="color:#e6db74">&#39;s/foo/bar/g&#39;</span> *.py
</span></span></code></pre></div><h2 id="the-performant-recursive-pattern">The Performant Recursive Pattern</h2>
<p>Here’s a performant command for making changes in the current directory and all subdirectories:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>find . -type f -name <span style="color:#e6db74">&#34;*.py&#34;</span> -exec sed -i <span style="color:#e6db74">&#39;s/old_function_name/new_function_name/g&#39;</span> <span style="color:#f92672">{}</span> +
</span></span></code></pre></div><p>Let me break this down because each piece matters more than you might think:</p>
<ul>
<li><code>find .</code> starts from the current directory</li>
<li><code>-type f</code> only matches files (not directories)</li>
<li><code>-name &quot;*.py&quot;</code> filters to Python files (adjust the pattern for your needs)</li>
<li><code>-exec sed -i 's/old/new/g' {} +</code> runs sed on batches of files</li>
</ul>
<p>That <code>+</code> at the end instead of <code>\;</code> is crucial for performance. It batches multiple files into each sed call instead of spawning a new process for every single file. When you’re dealing with thousands of files, this can be the difference between a 5-second operation and a 5-minute one.</p>
<h2 id="the-safer-version-i-actually-use">The Safer Version I Actually Use</h2>
<p>But in the real world, it might not be best to run that command as-is. Here&rsquo;s a more accidentally-had-decaf-proof version:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># First, see what we&#39;re dealing with</span>
</span></span><span style="display:flex;"><span>find . -type f -name <span style="color:#e6db74">&#34;*.py&#34;</span> -exec grep -l <span style="color:#e6db74">&#34;old_function_name&#34;</span> <span style="color:#f92672">{}</span> +
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Test on a single file first</span>
</span></span><span style="display:flex;"><span>find . -type f -name <span style="color:#e6db74">&#34;*.py&#34;</span> -exec grep -l <span style="color:#e6db74">&#34;old_function_name&#34;</span> <span style="color:#f92672">{}</span> + | head -1 | xargs sed -i.bak <span style="color:#e6db74">&#39;s/old_function_name/new_function_name/g&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># If that looks good, run on everything</span>
</span></span><span style="display:flex;"><span>find . -type f -name <span style="color:#e6db74">&#34;*.py&#34;</span> -exec sed -i.bak <span style="color:#e6db74">&#39;s/old_function_name/new_function_name/g&#39;</span> <span style="color:#f92672">{}</span> +
</span></span></code></pre></div><p>That <code>.bak</code> extension creates backup files automatically. Yes, you should be using version control, but I’ve seen too many scenarios where someone needed to quickly revert a change and of course they hadn&rsquo;t started with a clean working tree.</p>
<p>The backup files are easy to clean up later:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>find . -name <span style="color:#e6db74">&#34;*.bak&#34;</span> -delete
</span></span></code></pre></div><h2 id="when-gnu-sed-vs-bsd-sed-actually-matters">When GNU sed vs BSD sed Actually Matters</h2>
<p>Here’s something you run into when you switch from Linux to macOS: sed behaves differently. BSD sed (default on macOS) requires an argument to <code>-i</code>, even if it’s empty:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># Linux (GNU sed)</span>
</span></span><span style="display:flex;"><span>sed -i <span style="color:#e6db74">&#39;s/old/new/g&#39;</span> file.txt
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># macOS (BSD sed) - this breaks</span>
</span></span><span style="display:flex;"><span>sed -i <span style="color:#e6db74">&#39;s/old/new/g&#39;</span> file.txt
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># macOS (BSD sed) - this works</span>
</span></span><span style="display:flex;"><span>sed -i <span style="color:#e6db74">&#39;&#39;</span> <span style="color:#e6db74">&#39;s/old/new/g&#39;</span> file.txt
</span></span><span style="display:flex;"><span><span style="color:#75715e"># or with backup</span>
</span></span><span style="display:flex;"><span>sed -i <span style="color:#e6db74">&#39;.bak&#39;</span> <span style="color:#e6db74">&#39;s/old/new/g&#39;</span> file.txt
</span></span></code></pre></div><p>You can also write portable versions:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># Portable approach</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> sed --version 2&gt;/dev/null | grep -q GNU; <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>    find . -type f -name <span style="color:#e6db74">&#34;*.py&#34;</span> -exec sed -i <span style="color:#e6db74">&#39;s/old/new/g&#39;</span> <span style="color:#f92672">{}</span> +
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">else</span>
</span></span><span style="display:flex;"><span>    find . -type f -name <span style="color:#e6db74">&#34;*.py&#34;</span> -exec sed -i <span style="color:#e6db74">&#39;&#39;</span> <span style="color:#e6db74">&#39;s/old/new/g&#39;</span> <span style="color:#f92672">{}</span> +
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fi</span>
</span></span></code></pre></div><p>Or use the backup approach everywhere since it works on both:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>find . -type f -name <span style="color:#e6db74">&#34;*.py&#34;</span> -exec sed -i.bak <span style="color:#e6db74">&#39;s/old/new/g&#39;</span> <span style="color:#f92672">{}</span> +
</span></span></code></pre></div><h2 id="handling-special-characters-without-losing-your-mind">Handling Special Characters Without Losing Your Mind</h2>
<p>When your search string contains slashes, quotes, or regex metacharacters, things get interesting.</p>
<p>Instead of fighting with escaping, change the delimiter:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># Instead of this nightmare</span>
</span></span><span style="display:flex;"><span>sed -i <span style="color:#e6db74">&#39;s/https:\/\/old\.domain\.com\/api/https:\/\/new\.domain\.com\/api/g&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Use this</span>
</span></span><span style="display:flex;"><span>sed -i <span style="color:#e6db74">&#39;s|https://old.domain.com/api|https://new.domain.com/api|g&#39;</span>
</span></span></code></pre></div><p>You can use almost any character as the delimiter. I usually go with <code>|</code> for URLs and <code>#</code> for file paths or when I’m dealing with email addresses (it&rsquo;s easier to differentiate from a lowercase L).</p>
<p>For really complex patterns, sometimes it’s easier to put the sed script in a 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;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># In replace.sed</span>
</span></span><span style="display:flex;"><span>s|https://old.domain.com/api|https://new.domain.com/api|g
</span></span><span style="display:flex;"><span>s/DEBUG <span style="color:#f92672">=</span> True/DEBUG <span style="color:#f92672">=</span> False/g
</span></span><span style="display:flex;"><span>s/old_secret_key/new_secret_key/g
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Use it</span>
</span></span><span style="display:flex;"><span>find . -type f -name <span style="color:#e6db74">&#34;*.py&#34;</span> -exec sed -i.bak -f replace.sed <span style="color:#f92672">{}</span> +
</span></span></code></pre></div><p>This approach is also great for complex replacements that you’ll need to run multiple times or document for your team.</p>
<h2 id="performance-considerations-that-actually-matter">Performance Considerations That Actually Matter</h2>
<p>When you’re dealing with large codebases, performance starts to matter. Seemingly simple find-and-replace operations could take 20+ minutes on large repositories when done inefficiently.</p>
<p>The biggest performance killer is usually file selection. Don’t do this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># Slow—processes every file then filters</span>
</span></span><span style="display:flex;"><span>find . -type f -exec grep -l <span style="color:#e6db74">&#34;old_string&#34;</span> <span style="color:#f92672">{}</span> + | xargs sed -i <span style="color:#e6db74">&#39;s/old/new/g&#39;</span>
</span></span></code></pre></div><p>Do this instead:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># Fast—filters files first</span>
</span></span><span style="display:flex;"><span>find . -type f -name <span style="color:#e6db74">&#34;*.py&#34;</span> -exec sed -i <span style="color:#e6db74">&#39;s/old/new/g&#39;</span> <span style="color:#f92672">{}</span> +
</span></span></code></pre></div><p>If you need to be more selective about which files to process, use multiple find conditions:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># Only process Python files that aren&#39;t in virtual environments or build directories</span>
</span></span><span style="display:flex;"><span>find . -type f -name <span style="color:#e6db74">&#34;*.py&#34;</span> ! -path <span style="color:#e6db74">&#34;./venv/*&#34;</span> ! -path <span style="color:#e6db74">&#34;./build/*&#34;</span> ! -path <span style="color:#e6db74">&#34;./.git/*&#34;</span> -exec sed -i.bak <span style="color:#e6db74">&#39;s/old/new/g&#39;</span> <span style="color:#f92672">{}</span> +
</span></span></code></pre></div><h2 id="when-sed-isnt-the-right-tool">When sed Isn’t the Right Tool</h2>
<p>It&rsquo;s tempting to force sed to do things it’s not great at. Here’s when I reach for other tools:</p>
<p><strong>For complex transformations</strong>: Use a proper scripting language. A 50-line sed script could be 10 lines of Python and infinitely more readable.</p>
<p><strong>For structured data</strong>: If you’re modifying JSON, YAML, or XML, use tools that understand the format. sed doesn’t know about string escaping or nested structures.</p>
<p><strong>For very large files</strong>: sed loads the entire file into memory for each operation. For multi-gigabyte files, stream processing tools like awk might be better.</p>
<p><strong>For interactive replacements</strong>: Use your editor’s project-wide search and replace, or tools like <code>rg</code> (ripgrep) with interactive replacement.</p>
<h2 id="the-nuclear-option-parallel-processing">The Nuclear Option: Parallel Processing</h2>
<p>If you&rsquo;re dealing with truly massive codebases (millions of lines), you might need to get aggressive about performance:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># Find all target files first</span>
</span></span><span style="display:flex;"><span>find . -type f -name <span style="color:#e6db74">&#34;*.py&#34;</span> ! -path <span style="color:#e6db74">&#34;./venv/*&#34;</span> &gt; /tmp/files_to_process
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Process them in parallel</span>
</span></span><span style="display:flex;"><span>cat /tmp/files_to_process | xargs -n <span style="color:#ae81ff">50</span> -P <span style="color:#ae81ff">8</span> sed -i.bak <span style="color:#e6db74">&#39;s/old/new/g&#39;</span>
</span></span></code></pre></div><p>That <code>-P 8</code> runs up to 8 sed processes in parallel, and <code>-n 50</code> processes 50 files per batch. Adjust based on your CPU cores and I/O capacity.</p>
<h2 id="testing-before-you-commit">Testing Before You Commit</h2>
<p>Here’s a thorough testing workflow for large replacements:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># 1. Count occurrences before</span>
</span></span><span style="display:flex;"><span>find . -type f -name <span style="color:#e6db74">&#34;*.py&#34;</span> -exec grep -c <span style="color:#e6db74">&#34;old_string&#34;</span> <span style="color:#f92672">{}</span> + | awk -F: <span style="color:#e6db74">&#39;{sum+=$2} END {print sum}&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 2. Run replacement with backups</span>
</span></span><span style="display:flex;"><span>find . -type f -name <span style="color:#e6db74">&#34;*.py&#34;</span> -exec sed -i.bak <span style="color:#e6db74">&#39;s/old_string/new_string/g&#39;</span> <span style="color:#f92672">{}</span> +
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 3. Count occurrences after (should be 0)</span>
</span></span><span style="display:flex;"><span>find . -type f -name <span style="color:#e6db74">&#34;*.py&#34;</span> -exec grep -c <span style="color:#e6db74">&#34;new_string&#34;</span> <span style="color:#f92672">{}</span> + | awk -F: <span style="color:#e6db74">&#39;{sum+=$2} END {print sum}&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 4. Spot check a few files</span>
</span></span><span style="display:flex;"><span>find . -name <span style="color:#e6db74">&#34;*.bak&#34;</span> | head -5 | <span style="color:#66d9ef">while</span> read backup; <span style="color:#66d9ef">do</span>
</span></span><span style="display:flex;"><span>    original<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>backup%.bak<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>    echo <span style="color:#e6db74">&#34;=== </span>$original<span style="color:#e6db74"> ===&#34;</span>
</span></span><span style="display:flex;"><span>    diff <span style="color:#e6db74">&#34;</span>$backup<span style="color:#e6db74">&#34;</span> <span style="color:#e6db74">&#34;</span>$original<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">done</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 5. Run tests</span>
</span></span><span style="display:flex;"><span>make test  <span style="color:#75715e"># or whatever your test command is</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 6. If everything looks good, clean up backups</span>
</span></span><span style="display:flex;"><span>find . -name <span style="color:#e6db74">&#34;*.bak&#34;</span> -delete
</span></span></code></pre></div><h2 id="using-sed-in-real-world-scenarios">Using sed in Real-World Scenarios</h2>
<p><strong>API endpoint migration</strong>: Moving from v1 to v2 API endpoints meant updating hundreds of URL references across multiple repositories. The key was being selective about file types and using exact matches to avoid accidentally changing documentation or comments that mentioned the old API.</p>
<p><strong>Database migrations</strong>: After a database refactor for a Django application, sed came in handy for making changes to complex Django migration files. I used different sed patterns for different contexts—from Python to raw SQL—because the replacement patterns were slightly different in each case.</p>
<p><strong>Configuration key updates</strong>: When our configuration format changed, I needed to update key names across config files, code references, and documentation. This one required multiple passes with different patterns because the same logical key appeared in different syntactic contexts.</p>
<h2 id="the-debugging-workflow-that-saves-time">The Debugging Workflow That Saves Time</h2>
<p>When a sed operation goes wrong (and it will), here’s how I debug:</p>
<ol>
<li>
<p><strong>Check what files were actually modified</strong>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>find . -name <span style="color:#e6db74">&#34;*.bak&#34;</span> -exec sh -c <span style="color:#e6db74">&#39;diff -q &#34;$1&#34; &#34;${1%.bak}&#34;&#39;</span> _ <span style="color:#f92672">{}</span> <span style="color:#ae81ff">\;</span> | head -10
</span></span></code></pre></div></li>
<li>
<p><strong>Look for unintended matches</strong>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>find . -name <span style="color:#e6db74">&#34;*.bak&#34;</span> -exec sh -c <span style="color:#e6db74">&#39;diff &#34;$1&#34; &#34;${1%.bak}&#34;&#39;</span> _ <span style="color:#f92672">{}</span> <span style="color:#ae81ff">\;</span> | grep <span style="color:#e6db74">&#34;^&lt;&#34;</span> | sort | uniq -c | sort -nr
</span></span></code></pre></div></li>
<li>
<p><strong>Restore and try a more specific pattern</strong>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>find . -name <span style="color:#e6db74">&#34;*.bak&#34;</span> -exec sh -c <span style="color:#e6db74">&#39;mv &#34;$1&#34; &#34;${1%.bak}&#34;&#39;</span> _ <span style="color:#f92672">{}</span> <span style="color:#ae81ff">\;</span>
</span></span></code></pre></div></li>
</ol>
<p>The pattern of creating backups, testing the results, and having a quick rollback strategy will save you countless hours. It’s especially important when you’re working on shared codebases where a mistake affects your entire team.</p>
<p>While sed operations might seem like they&rsquo;re just for simple text processing, they can help with critical steps in deployments, migrations, and refactoring efforts that affect real systems and real users. Taking the time to do them safely and efficiently pays dividends when you’re not scrambling to fix broken builds or track down subtle bugs that only show up in production.</p>
<p>If you found some value in this post, there&rsquo;s more! I write about high-output development processes and building maintainable systems in the AI age. You can get my posts in your inbox by subscribing below.</p>
<a href="https://medium.com/@victoriadotdev/subscribe" target="_blank" rel="noopener noreferrer" class="subscribe-button">
    <span class="subscribe-icon">📧</span>
    <span class="subscribe-text">Subscribe</span>
</a>

]]></content></entry><entry><title type="html">Things you need to know about becoming a Data Scientist</title><link href="https://victoria.dev/archive/things-you-need-to-know-about-becoming-a-data-scientist/"/><id>https://victoria.dev/archive/things-you-need-to-know-about-becoming-a-data-scientist/</id><author><name>Victoria Drake</name></author><published>2017-03-31T13:19:19+09:00</published><updated>2017-03-31T13:19:19+09:00</updated><content type="html"><![CDATA[<p>I recently attended a panel discussion hosted by General Assembly in Singapore entitled, &ldquo;So you want to be a Data Scientist/Analyst&rdquo;. The panel featured professionals in different stages of their careers and offered a wealth of information to an audience of hopefuls, including tips on how to land a job as a data scientist, and stories debunking myths that color this field.</p>
<h2 id="the-panelists">The panelists</h2>
<ul>
<li><strong>Misrab Faizullah-Khan</strong> - VP of Data Science, <em>GO_JEK</em></li>
<li><strong>Anthony Ta</strong> - Data Scientist, <em>Tech in Asia</em></li>
<li><strong>Leow Guo Jun</strong> - Data Scientist, <em>GO_JEK</em></li>
<li><strong>Gabriel Jiang</strong> - Data Scientist</li>
<li><strong>Adam Drake</strong> - Chief Data Officer, <em>Atazzo</em></li>
</ul>
<p>Here&rsquo;s a rundown of the major points discussed, paraphrased for brevity.</p>
<h3 id="whats-a-day-in-the-life-like">What&rsquo;s a day-in-the-life like</h3>
<p>We&rsquo;re mostly &ldquo;data janitors.&rdquo; A large part of working with data begins with and consists of data sanitation. Without quality data, you won&rsquo;t get accurate results. Understanding how data should be sanitized largely encompasses skills that aren&rsquo;t directly related to data analytics. To fully understand the problem you&rsquo;re hoping to solve, you need to talk with the people involved. It&rsquo;s important that everyone understands all the elements of a project, and exactly what those elements are being called. &ldquo;Sales,&rdquo; as an example, may be calculated differently depending on who you&rsquo;re talking to.</p>
<h3 id="whats-a-data-scientist-vs-data-analyst">What&rsquo;s a data &ldquo;scientist&rdquo; vs. data &ldquo;analyst&rdquo;</h3>
<p>It largely depends on the company you work for. &ldquo;Data [insert modifier]&rdquo; is only a recent distinction for a job field that has historically been called &ldquo;Business Analytics.&rdquo; In a smaller company, as with any other position, one person may handle a variety of data-related tasks under the title of &ldquo;Data Scientist.&rdquo; In a larger company with more staff and finer grain specialization, you may have a &ldquo;Data Analyst&rdquo; that handles less technical aspects, and a &ldquo;Data Scientist&rdquo; whose work is very technical and involves quantitative learning or machine learning.</p>
<p>The field of data science/analytics is fresh enough that standard definitions for job titles really haven&rsquo;t been agreed upon yet. When considering a position, focus on the company rather than the title.</p>
<h3 id="should-i-join-a-startup-or-large-company">Should I join a startup or large company</h3>
<p>There&rsquo;s no wrong answer. Being aware of your own working style and preferences will help guide your decision.</p>
<p>Startups generally offer more freedom and less micromanaging. This also means that you&rsquo;ll necessarily receive less guidance, and will need to be able to figure stuff out, learn, and make progress under your own power.</p>
<p>In a big company, you&rsquo;re likely to experience more structure, and be expected to follow very clearly defined pre-existing processes. Your job scope will likely be more focused than it would be at a startup. You&rsquo;ll experience less freedom in general, but also more certainty in what&rsquo;s expected of you.</p>
<p>In the end, especially at the beginning of your career, don&rsquo;t put too much stock in choosing one or the other. If you like the company, big or small, give it a try. If you&rsquo;re not happy there after a few months, then try another one. No career decision is ever permanent.</p>
<p>It&rsquo;s also worthwhile to note that even if you find a company you like the first time around, it&rsquo;s in your best interest to change companies after one or two years. The majority of the salary raises you&rsquo;ll earn in your lifetime will occur in the first ten years of your career. Say you&rsquo;re hired by Company A as a junior data scientist for two years - after two years, you&rsquo;re no longer a junior. You can now earn, say, a 30% higher salary in a data scientist position, but it&rsquo;s unlikely that Company A will give you a 30% raise after two years. At that point it&rsquo;s time to find Company B and put a few more years of experience on your resume, then probably change companies again. You don&rsquo;t earn the big bucks sticking with one company for decades - you&rsquo;ll always be the junior developer.</p>
<p><img src="datasci-offstage.jpg" alt="Talking offstage."></p>
<h3 id="what-do-you-look-for-when-hiring-a-candidate">What do you look for when hiring a candidate</h3>
<p>Overall, the most important skills for a data science candidate are soft skills. Curiosity, tenacity, and good communication skills are vital. Persistence, especially when it comes to adapting to a quickly changing industry, is important. The most promising candidates are passionate enough about the field to be learning everything they can, even outside of their work scope. Hard skills like coding and algorithms can be taught - it&rsquo;s the soft skills that set good candidates apart.</p>
<p>Hacking skills are also vital. This doesn&rsquo;t necessarily mean you can write code. Someone who has a grasp of overall concepts, knows algorithms, and has curiosity enough to continuously learn is going to go farther than someone who can just write code. It takes creativity to build hacking skills on top of being familiar with the basic navigation points. Having the ability to come up with solutions that use available tools in new ways - that&rsquo;s hacking skill.</p>
<p>Design thinking is another important asset. Being able to understand how systems integrate on both technical and business levels is very valuable. If you&rsquo;re able to see the big picture, you&rsquo;re more likely to find different ways to accomplish the overall objective.</p>
<p>You might think that seeing buzzwords on resumes makes you look more attractive as a candidate - more often, it stands out as a red flag. Putting &ldquo;advanced machine learning&rdquo; on your CV and then demonstrating that you don&rsquo;t know basic algorithms doesn&rsquo;t look good. It&rsquo;s your projects and your interests outside of the job you&rsquo;re applying for that say the most about you. Popular topics in this industry change fast - you&rsquo;re better off having a solid grasp of basic fundamentals as well as a broad array of experience than name-dropping whatever&rsquo;s trending.</p>
<h3 id="is-there-a-future-for-humans-in-the-data-science-field-when-will-the-machines-replace-us">Is there a future for humans in the data science field? When will the machines replace us</h3>
<p>This isn&rsquo;t a question unique to data science, and many historical examples already exist. Financial investment is a good example - where you used to have a human do calculations and make predictions, computers now do a lot of that automatically, making decisions about risk and possible payoff every day.</p>
<p>Where humans won&rsquo;t be replaced, just as in other industries that have embraced automation, is in the human element. You&rsquo;ll still need people to handle communication, be creative, be curious, make interpretations and understand problems&hellip; all those things are fundamentally human aspects of enterprise.</p>
<p>Ultimately, machines and more automation will make human work less of a grind. By automating the mundane stuff, like data sanitization for example, human minds are freed up to develop more interesting things.</p>
<h3 id="what-are-the-future-applications-for-data-driven-automation">What are the future applications for data-driven automation</h3>
<p>Legal is a good next candidate for automation. There&rsquo;s a lot there that can be handled by programs using data to assess risk.</p>
<p>Medicine is another field ripe for advances through data. Radiologists, your days are numbered: image detection is coming for you. The whole field of diagnostics is about to drastically change.</p>
<p>A particularly interesting recent application for data science is in language translation. By looking at similarities in sentence structure and colloquial speech across different languages, we&rsquo;re able to sort similar words based on the &ldquo;space&rdquo; they occupy within the language structure.</p>
<p>Insurance - the original data science industry - already is and will continue to become very automated. With increased ability to use data to assess risk, we&rsquo;re beginning to see new creative insurance products being introduced. E-commerce companies can now buy insurance on the risk a customer will return a product - hard to do without the accessibility of data that we have today.</p>
<h3 id="how-do-i-push-data-driven-decisions-and-get-my-boss-to-agree-with-me">How do I push data-driven decisions and get my boss to agree with me</h3>
<p>It&rsquo;s a loaded question. The bottom line is that it depends on the company&rsquo;s data culture and decision path. We&rsquo;ve experienced working for management who say, &ldquo;We&rsquo;ve already made the decisions, we just need the data to prove it.&rdquo; Obviously, that&rsquo;s a tough position to work from.</p>
<p>Generally, ask yourself, &ldquo;Am I making my boss look good?&rdquo; You might hear that and think, &ldquo;Why would I let my boss get all the credit?&rdquo; - but who cares? Let them take the credit. If you&rsquo;re producing good work, you&rsquo;re making your team look good. If you make your team look good, you&rsquo;re indispensible to your team and your boss. People who are indispensible are listened to.</p>
<h3 id="whats-your-best-advice-for-a-budding-data-scientist">What&rsquo;s your best advice for a budding data scientist</h3>
<p>Don&rsquo;t be too keen to define yourself too quickly. If you narrow your focus too much, especially when you&rsquo;re learning, you can get stuck in a situation of having become an expert in &ldquo;Technology A, version 3&rdquo; when companies are looking to hire for experts in version 4. It happens.</p>
<p>A broad understanding of fundamentals will be far more valuable to you on the whole. Maybe you start out writing code, and decide you don&rsquo;t like it, but discover that you&rsquo;re really good at designing big picture stuff and leading teams, and you end up as a technical lead. It could even vary depending on the company you work for - so stay flexible.</p>
<p>Your best bet is to follow what you&rsquo;re passionate about, and try to understand a wide range of overall concepts. Spend the majority of your efforts learning things that are timeless, like the base technologies under hot-topic items like TensorFlow. Arm yourself with a broad understanding of the terrain, different companies, and the products that are out there.</p>
<p>If you focus on learning code specifically, learning one language well makes it easier to learn others. Make sure you understand the basics.</p>
<h3 id="tldr-it">TL;dr it</h3>
<ul>
<li><strong>Adam:</strong> Talk more and don&rsquo;t give up.</li>
<li><strong>Anthony:</strong> [Be] courageous, and hands-on.</li>
<li><strong>Gabriel:</strong> Be creative.</li>
<li><strong>Guo Jun:</strong> It&rsquo;s worth the pain.</li>
<li><strong>Misrab:</strong> Evaluate yourself and maintain a feedback loop.</li>
</ul>
<p><img src="datasci-crowd.jpg" alt="The crowd at GA Singapore"></p>
]]></content></entry><entry><title type="html">How I created custom desktop notifications using terminal and cron</title><link href="https://victoria.dev/archive/how-i-created-custom-desktop-notifications-using-terminal-and-cron/"/><id>https://victoria.dev/archive/how-i-created-custom-desktop-notifications-using-terminal-and-cron/</id><author><name>Victoria Drake</name></author><published>2017-02-21T10:48:38+07:00</published><updated>2017-02-21T10:48:38+07:00</updated><content type="html"><![CDATA[<p>In my last post I talked about moving from Windows 10 to running i3 on Linux, built up from Debian Base System. Among other things, this change has taught me about the benefits of using basic tools and running a minimal, lightweight system. You can achieve a lot of functionality with just command line tools and simple utilities. One example I&rsquo;d like to illustrate in this post is setting up desktop notifications.</p>
<p>I use <a href="https://dunst-project.org/">dunst</a> for desktop notifications. It&rsquo;s a simple, lightweight tool that is easy to configure, doesn&rsquo;t have many dependencies, and can be used across various distributions.</p>
<h2 id="battery-statuslow-battery-notification">Battery status/low battery notification</h2>
<p>I was looking for a simple, versatile set up to create notifications for my battery status without having to rely on separate, standalone GUI apps or services. In my search I came across a simple one-line cron task that seemed to be the perfect fit. I adapted it to my purpose and it looks like this:</p>
<pre tabindex="0"><code class="language-conf" data-lang="conf"># m h  dom mon dow   command
*/5 * * * * acpi --battery | awk -F, &#39;/Discharging/ { if (int($2) &lt; 20) print }&#39; | xargs -ri env DISPLAY=:0 notify-send -u critical -i &#34;/usr/share/icons/Paper/16x16/status/xfce-battery-critical.png&#34; -t 3000 &#34;{}\nBattery low!&#34;
</code></pre><blockquote>
<p><em>Psst&hellip; <a href="https://crontab.guru/">here&rsquo;s a great tool</a> for formatting your crontab times.</em></p>
</blockquote>
<p>There&rsquo;s a lot going on here, so let&rsquo;s break it down:
<code>*/5 * * * *</code>
Every five minutes, do the following.</p>
<p><code>acpi --battery</code>
Execute <code>acpi</code> and show battery information, which on its own returns something akin to:
<code>Battery 0: Discharging, 65%, 03:01:27 remaining</code></p>
<p>Pretty straightforward so far. At any point you could input <code>acpi --battery</code> in a terminal and receive the status output. Today&rsquo;s post, however, is about receiving this information passively in a desktop notification. So, moving on:</p>
<p><code>| awk -F, '/Discharging/ { if (int($2) &lt; 20) print }'</code>
Pipe (<code>|</code>) the result of the previous command to <code>awk</code>. (If you don&rsquo;t know what pipe does, here&rsquo;s <a href="http://superuser.com/questions/756158/what-does-the-linux-pipe-symbol-do">an answer from superuser.com</a> that explains it pretty well, I think.) <code>awk</code> can do a lot of things, but in this case, we&rsquo;re using it to examine the status of our battery. Let&rsquo;s zoom in on the <code>awk</code> command:</p>
<p><code>awk -F, '/Discharging/ { if (int($2) &lt; 20) print }'</code>
Basically, we&rsquo;re saying, &ldquo;Hey, awk, look at that input you just got and try to find the word &ldquo;discharging,&rdquo; then look to see if the number after the first comma is less than 20. If so, print the whole input.&rdquo;</p>
<p><code>| xargs -ri</code>
Pipe the result of the previous command to <code>xargs</code>, which takes it as its input and does more stuff with it. <code>-ri</code> is equivalent to <code>-r</code> (run the next command only if it receives arguments) and <code>-i</code> (look for &ldquo;{}&rdquo; and replace it with the input). So in this example, xargs serves as our gatekeeper and messenger for the next command.</p>
<p><code>env DISPLAY=:0</code>
Run the following utility in the specified display, in this case, the first display of the local machine.</p>
<p><code>notify-send -u critical -i &quot;/usr/share/icons/Paper/16x16/status/xfce-battery-critical.png&quot; -t 3000 &quot;{}\nLow battery!&quot;</code>
Shows a desktop notification with <code>-u critical</code> (critical urgency), <code>-i</code> (the specified icon), <code>-t 3000</code> (display time/expires after 3000 milliseconds), and finally <code>{}</code> (the output of awk, replaced by xargs).</p>
<p>Not bad for a one-liner! I made a few modifications for different states of my battery. Here they all are in my crontab:</p>
<pre tabindex="0"><code class="language-conf" data-lang="conf"># m h  dom mon dow   command
*/5 * * * * acpi --battery | awk -F, &#39;/Discharging/ { if ( (int($2) &lt; 30) &amp;&amp; (int($2) &gt; 15) ) print }&#39; | xargs -ri env DISPLAY=:0 notify-send -a &#34;Battery status&#34; -u normal -i &#34;/usr/share/icons/Paper/16x16/status/xfce-battery-low.png&#34; -t 3000 &#34;{}\nBattery low!&#34;
*/5 * * * * acpi --battery | awk -F, &#39;/Discharging/ { if (int($2) &lt; 15) print }&#39; | xargs -ri env DISPLAY=:0 notify-send -a &#34;Battery status&#34; -u critical -i &#34;/usr/share/icons/Paper/16x16/status/xfce-battery-critical.png&#34; -t 3000 &#34;{}\nSeriously, plug me in.&#34;
*/60 * * * * acpi --battery | awk -F, &#39;/Discharging/ { if (int($2) &gt; 30) print }&#39; | xargs -ri env DISPLAY=:0 notify-send -a &#34;Battery status&#34; -u normal -i &#34;/usr/share/icons/Paper/16x16/status/xfce-battery-ok.png&#34; &#34;{}&#34;
*/60 * * * * acpi --battery | awk -F, &#39;/Charging/ { print }&#39; | xargs -ri env DISPLAY=:0 notify-send -a &#34;Battery status&#34; -u normal -i &#34;/usr/share/icons/Paper/16x16/status/xfce-battery-ok-charging.png&#34; &#34;{}&#34;
*/60 * * * * acpi --battery | awk -F, &#39;/Charging/ { if (int($2) == 100) print }&#39; | xargs -ri env DISPLAY=:0 notify-send -a &#34;Battery status&#34; -u normal -i &#34;/usr/share/icons/Paper/16x16/status/xfce-battery-full-charging.png&#34; &#34;Fully charged.&#34;
</code></pre><p>By the way, you can open your crontab in the editor of your choice by accessing it as root from the <code>/var/spool/cron/crontabs/</code> directory. It&rsquo;s generally best practice however to make changes to your crontab with the command <code>crontab -e</code>.</p>
<p>You can see that each notification makes use of the <code>{}</code> placeholder that tells xargs to put its input there - except for the last one. This is interesting because in this case, we&rsquo;re only using <code>xargs -ri</code> as a kind of switch to present the notification. The actual information that was the input for xargs is not needed in the output in order to create a notification.</p>
<h2 id="additional-notifications-with-command-line-tools">Additional notifications with command line tools</h2>
<p>With cron and just a few combinations of simple command line tools, you can create interesting and useful notifications. Consider the following:</p>
<h3 id="periodically-check-your-dhcp-address">Periodically check your dhcp address</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>*/60 * * * * journalctl | awk -F: <span style="color:#e6db74">&#39;/dhcp/ &amp;&amp; /address/ { print $5 }&#39;</span> | tail -1 | xargs -ri env DISPLAY<span style="color:#f92672">=</span>:0 notify-send -a <span style="color:#e6db74">&#34;dhcp address&#34;</span> -u normal <span style="color:#e6db74">&#34;{}&#34;</span>
</span></span></code></pre></div><p>Which does the following:
<code>*/60 * * * *</code>
Every 60 minutes.</p>
<p><code>journalctl</code>
Take the contents of your system log.</p>
<p><code>| tail -1'/dhcp/ &amp;&amp; /address/ { print $5 }'</code>
Find logs containing both &ldquo;dhcp&rdquo; and &ldquo;address&rdquo; and output the 5th portion as separated by &ldquo;:&rdquo; (the time field counts).</p>
<p><code>| tail -1</code>
Take the last line of the output.</p>
<p><code>| xargs -ri env DISPLAY=:0 notify-send -a &quot;dhcp address&quot; -u normal &quot;{}&quot;</code>
Create the desktop notification including the output.</p>
<h3 id="periodically-display-the-time-and-date">Periodically display the time and date</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>*/60 * * * * timedatectl status | awk -F<span style="color:#ae81ff">\n</span> <span style="color:#e6db74">&#39;/Local time/ { print }&#39;</span> | xargs -ri env DISPLAY<span style="color:#f92672">=</span>:0 notify-send -a <span style="color:#e6db74">&#34;Current Time&#34;</span> -u normal <span style="color:#e6db74">&#34;{}&#34;</span>
</span></span></code></pre></div><h3 id="system-log-activity">System log activity</h3>
<p>You can also search your system logs (try <code>journalctl</code>) for any number of things using awk, enabling you to get periodic notifications of virtually any logged events.</p>
<h2 id="experiment">Experiment</h2>
<p>As with all things, you are only limited by your imagination! I hope this post has given you some idea about the endless possibilities of these simple utilities. Thanks for reading!</p>
]]></content></entry><entry><title type="html">How I ditched WordPress and set up my custom domain HTTPS site for (almost) free</title><link href="https://victoria.dev/archive/how-i-ditched-wordpress-and-set-up-my-custom-domain-https-site-for-almost-free/"/><id>https://victoria.dev/archive/how-i-ditched-wordpress-and-set-up-my-custom-domain-https-site-for-almost-free/</id><author><name>Victoria Drake</name></author><published>2017-01-28T13:16:17+07:00</published><updated>2017-01-28T13:16:17+07:00</updated><content type="html"><![CDATA[<p>I got annoyed with WordPress.com. While using the service has its pros (like https and a mobile responsive website, and being very visual and beginner-friendly) it&rsquo;s limiting. For someone who&rsquo;s comfortable enough to be tweaking CSS but who&rsquo;s not interested in creating their own theme (or paying upwards of $50 for one), I felt I wasn&rsquo;t really the type of consumer WordPress.com was suited to.</p>
<p>To start with, if you want to remove WordPress advertising and use a custom domain name, it&rsquo;s a minimum of $3 per month. If, like me, the free themes provided aren&rsquo;t just what you&rsquo;re looking for, you&rsquo;re stuck with two choices: buy a theme for $50+, or pay $8.25 per month to do <em>some</em> css customization. I don&rsquo;t know about you, but I feel like there should be a hack for this.</p>
<h2 id="how-i-ditched-wordpress-and-got-everything-i-wanted-for-free">How I ditched WordPress and got everything I wanted for free</h2>
<p>Okay, <em>almost</em> free. You still have to pay <a href="https://www.tkqlhce.com/click-100268310-14326263">at least $0.99</a> for a domain name.</p>
<p>For those of you technical enough to skip reading a long post, the recipe is this:</p>
<ol>
<li>Buy a <a href="https://www.jdoqocy.com/ds70r09608OQPPRVXSQPOQSRVVVVX" target="_top">custom domain via this Namecheap affiliate link</a>
 (Thanks for your support! 😊)</li>
<li>Install <a href="https://www.gohugo.io/">Hugo</a>, my favorite static site generator</li>
<li>Host with <a href="https://pages.github.com/">GitHub Pages</a></li>
<li>Put your <a href="https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/managing-a-custom-domain-for-your-github-pages-site#configuring-an-alias-or-aname-record-with-your-dns-provider">custom domain to work with GitHub Pages</a></li>
<li><del>Use Cloudflare&rsquo;s free plan</del> <a href="#5-enforce-https-for-github-pages">Enforce HTTPS for GitHub Pages</a></li>
</ol>
<p>Let&rsquo;s do the nitty gritty:</p>
<h3 id="1-buy-a-custom-domain">1. Buy a custom domain</h3>
<p>This one&rsquo;s pretty simple. Head on over to <a href="https://www.jdoqocy.com/ds70r09608OQPPRVXSQPOQSRVVVVX" target="_top">Namecheap</a>
, <a href="https://www.gandi.net">Gandi</a>, or if you&rsquo;re rolling in dough, <a href="https://www.godaddy.com/">GoDaddy</a>. Find your perfect web address and buy it up.</p>
<p>If it&rsquo;s a personal domain like <em>yourname.com,</em> it&rsquo;s a pretty good idea to pay upfront for five years or even ten years, if you&rsquo;ve got the cash. It&rsquo;ll save you the trouble of remembering to renew, allow you to build your personal brand, and prevent someone else from buying up your URL.</p>
<p>If you&rsquo;re just trying out an idea, you can go with a one-year <a href="https://www.tkqlhce.com/click-100268310-14326263">$0.99 experiment</a>. Namecheap also gives you WhoisGuard (domain registration privacy) free for one year.</p>
<h3 id="2-install-hugo">2. Install Hugo</h3>
<p>I&rsquo;m a big fan of <a href="https://www.gohugo.io/">Hugo</a> so far. Admittedly, those who feel more comfortable with a visual, WYSIWYG editor may feel like a fish out of water at first. As long as you&rsquo;re not afraid of using command line, though, using Hugo is pretty straightforward. The fact that I have access to all my code is my favorite part. It&rsquo;s only as simple or complicated as I want it to be.</p>
<p>Hugo is open source and free. They&rsquo;ve got great documentation, and following their <a href="https://gohugo.io/overview/quickstart/">Quickstart guide</a> line-by-line will get you set up with your new site in minutes.</p>
<p>If you&rsquo;re not used to the idea of your site existing as files and folders, the basic premise is this: Hugo, along with the themes available, helps you to create all the pages and files that your site needs to run.</p>
<p>Blog posts can be written in Markdown and saved in your <code>/content/blog/</code> folder; preferences for your site and theme can be set in the <code>config.toml</code> file. After that, generating all your site&rsquo;s pages is as quick and easy as typing the command <code>hugo --theme=&lt;your theme&gt;</code>. You&rsquo;ll be able to see a live version of your site in your browser as you&rsquo;re editing it (go to <code>http://localhost:1313/</code> in your browser, as described in Step 5) so you&rsquo;re not flying blind.</p>
<h3 id="3-host-with-github-pages">3. Host with GitHub Pages</h3>
<p>If you read to Step 12 of Hugo&rsquo;s Quickstart Guide, you&rsquo;ll see that they even provided instructions for hosting your files on GitHub pages. If you&rsquo;re new to Git, you&rsquo;ll first need to <a href="https://github.com/">sign up at GitHub</a> and then <a href="https://docs.github.com/en/get-started/quickstart/set-up-git">set up Git</a>. GitHub is a very friendly resource, and you can find a multitude of code examples and guides in connection with it. The <a href="https://docs.github.com/en/get-started/quickstart/hello-world">Hello World Guide</a> will take you through all you need to know to use GitHub.com.</p>
<p>Once you&rsquo;re comfortable with the way GitHub works generally, setting up a site by following <a href="https://pages.github.com/">the guide on GitHub Pages</a> is no big deal. If you followed the Hugo Quickstart Guide up to Step 11, you&rsquo;ll want to jump to Step 12 after creating the repository on GitHub.</p>
<p>In case it&rsquo;s not clear, once you set up your new repository on GitHub called <em>yourusername</em>.github.io, grab the HTTPS link at the top. From there it&rsquo;s just a few simple commands to create the git repository for your site and push it to your new web address:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span><span style="color:#75715e">## from yoursite/public folder:</span>
</span></span><span style="display:flex;"><span>$ git init
</span></span><span style="display:flex;"><span>$ git remote add origin &lt;paste that https url here!&gt;
</span></span><span style="display:flex;"><span>$ git add --all
</span></span><span style="display:flex;"><span>$ git commit -m <span style="color:#e6db74">&#34;Initial commit.&#34;</span>
</span></span><span style="display:flex;"><span>$ git push origin master
</span></span></code></pre></div><p>Have a little celebration - your site is already up at <code>https://yourusername.github.io</code>! Now for the pizza-de-resilience: the custom domain.</p>
<h3 id="4-point-your-custom-domain-to-github-pages">4. Point your custom domain to GitHub Pages</h3>
<p>To set up your site at apex (meaning <code>yourname.com</code> will replace <code>yourusername.github.io</code>), there&rsquo;s just four steps:</p>
<ol>
<li><a href="https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/managing-a-custom-domain-for-your-github-pages-site">Add your domain to your GitHub Pages site repository</a></li>
<li>In your domain registrar&rsquo;s DNS settings, <a href="https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/managing-a-custom-domain-for-your-github-pages-site#configuring-an-apex-domain">create A records pointing to GitHub&rsquo;s IP addresses</a></li>
<li>In your domain registrar&rsquo;s DNS settings, create a CNAME record pointing to <code>yourusername.github.io</code></li>
<li>Make sure there&rsquo;s a CNAME file in the root directory of your GitHub repository containing <code>yourname.com</code> (your custom domain)</li>
</ol>
<h3 id="5-enforce-https-for-github-pages">5. Enforce HTTPS for GitHub Pages</h3>
<p><a href="https://blog.github.com/2018-05-01-github-pages-custom-domains-https/">GitHub Pages supports HTTPS</a> through a partnership with <a href="https://letsencrypt.org/">Let&rsquo;s Encrypt</a>! This greatly simplifies the process of serving your site securely. Just look for this clever checkbox in the Settings of your site&rsquo;s GitHub repository.</p>
<p><img src="custom-domain-https.png#screenshot" alt="Enforce HTTPS checkbox"></p>
<p><em>Why do I need HTTPS anyway?</em> For one, it&rsquo;ll <a href="http://searchengineland.com/google-starts-giving-ranking-boost-secure-httpsssl-sites-199446/">give your site a little boost on Google</a>. More importantly, it&rsquo;s fundamental to your website security. You can <a href="/blog/what-is-tls-transport-layer-security-encryption-explained-in-plain-english/">learn more about HTTPS and TLS in this post</a>.</p>
<p>That&rsquo;s pretty much it! If you don&rsquo;t see changes right away, give all your services a lunch hour or so to propogate. Soon your site will be up and running at <code>https://yourname.com</code>.</p>
<p>Thanks for reading! If you found this post helpful, there&rsquo;s a lot more where this came from. You can subscribe below to see new posts first.</p>
<a href="https://medium.com/@victoriadotdev/subscribe" target="_blank" rel="noopener noreferrer" class="subscribe-button">
    <span class="subscribe-icon">📧</span>
    <span class="subscribe-text">Subscribe</span>
</a>

]]></content></entry><entry><title type="html">Iteration in Python: for, list, and map</title><link href="https://victoria.dev/archive/iteration-in-python-for-list-and-map/"/><id>https://victoria.dev/archive/iteration-in-python-for-list-and-map/</id><author><name>Victoria Drake</name></author><published>2017-01-18T21:58:28+07:00</published><updated>2020-11-22T21:58:28+07:00</updated><content type="html"><![CDATA[<p>Iteration in Python can be a little hard to understand. Subtle differences in terminology like <em>iteration</em>, <em>iterator</em>, <em>iterating</em>, and <em>iterable</em> aren&rsquo;t the most beginner-friendly.</p>
<p>When tackling new concepts, I find concrete examples to be most useful. I&rsquo;ll share some in this post and discuss appropriate situations for each. (Pun intended.)</p>
<h2 id="for-loop">For loop</h2>
<p>First, in pseudocode:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">for</span> iterating_variable <span style="color:#f92672">in</span> iterable:
</span></span><span style="display:flex;"><span>    statement(s)
</span></span></code></pre></div><p>I find <code>for</code> loops to be the most readable way to iterate in Python. This is especially nice when you&rsquo;re writing code that someone else needs to read and understand, which is always.</p>
<p>An <code>iterating_variable</code>, loosely speaking, is anything you could put in a group. For example: a letter in a string, an item from a list, or an integer in a range of integers.</p>
<p>An <code>iterable</code> houses the things you iterate on. This can also take different forms: a string with multiple characters, a range of numbers, a list, and so on.</p>
<p>A <code>statement</code> or multiple <code>statements</code> indicates <em>doing something</em> to the iterating variable.  This could be anything from mathematical expressions to simply printing a result.</p>
<p>Here are a couple simple examples that print each <code>iterating_variable</code> of an <code>iterable</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">for</span> letter <span style="color:#f92672">in</span> <span style="color:#e6db74">&#34;Hello world&#34;</span>:
</span></span><span style="display:flex;"><span>    print(letter)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> i <span style="color:#f92672">in</span> range(<span style="color:#ae81ff">10</span>):
</span></span><span style="display:flex;"><span>    print(i)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>breakfast_menu <span style="color:#f92672">=</span> [<span style="color:#e6db74">&#34;toast&#34;</span>, <span style="color:#e6db74">&#34;eggs&#34;</span>, <span style="color:#e6db74">&#34;waffles&#34;</span>, <span style="color:#e6db74">&#34;coffee&#34;</span>]
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> choice <span style="color:#f92672">in</span> breakfast_menu:
</span></span><span style="display:flex;"><span>    print(choice)
</span></span></code></pre></div><p>You can even use a <code>for</code> loop in a more compact situation, such as this one-liner:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>breakfast_buffet <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34; &#34;</span><span style="color:#f92672">.</span>join(str(item) <span style="color:#66d9ef">for</span> item <span style="color:#f92672">in</span> breakfast_menu)
</span></span></code></pre></div><p>The downside to <code>for</code> loops is that they can be a bit verbose, depending on how much you&rsquo;re trying to achieve. Still, for anyone hoping to make their Python code as easily understood as possible, <code>for</code> loops are the most straightforward choice.</p>
<h2 id="list-comprehensions">List comprehensions</h2>
<p>A pseudocode example:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>new_list <span style="color:#f92672">=</span> [statement(s) <span style="color:#66d9ef">for</span> iterating_variable <span style="color:#f92672">in</span> iterable]
</span></span></code></pre></div><p>List comprehensions are a concise and elegant way to create a new list by iterating on variables. Once you have a grasp of how they work, you can perform efficient iterations with very little code.</p>
<p>List comprehensions will always return a list, which may or may not be appropriate for your situation.</p>
<p>For example, you could use a list comprehension to quickly calculate and print tip percentage on a few bar tabs at once:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>tabs <span style="color:#f92672">=</span> <span style="color:#f92672">[</span>23.60, 42.10, 17.50<span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span>tabs_incl_tip <span style="color:#f92672">=</span> <span style="color:#f92672">[</span>round<span style="color:#f92672">(</span>tab*1.15, 2<span style="color:#f92672">)</span> <span style="color:#66d9ef">for</span> tab in tabs<span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span>print<span style="color:#f92672">(</span>tabs_incl_tip<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>&gt;&gt;&gt; <span style="color:#f92672">[</span>27.14, 48.41, 20.12<span style="color:#f92672">]</span>
</span></span></code></pre></div><p>In one concise line, we&rsquo;ve taken each tab amount, added a 15% tip, rounded it to the nearest cent, and made a new list of the tabs plus the tip values.</p>
<p>List comprehensions can be an elegant tool if output to a list is useful to you. Be advised that the more statements you add, the more complicated your list comprehension begins to look, especially once you get into nested list comprehensions. If your code isn&rsquo;t well annotated, it may become difficult for another reader to figure out.</p>
<h2 id="map">Map</h2>
<p>How to <code>map</code>, in pseudocode:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>map(statement, iterable)
</span></span></code></pre></div><p>Map is pretty compact, for better or worse. It can be harder to read and understand, especially if your line of code has a lot of parentheses.</p>
<p>In terms of efficiency for character count, <code>map</code> is hard to beat. It applies your <code>statement</code> to every instance of your <code>iterable</code> and returns an iterator.</p>
<p>Here&rsquo;s an example casting each element of <code>input()</code> (the iterable) from string representation to integer representation. Since <code>map</code> returns an iterator, you also cast the result to a list representation.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>values <span style="color:#f92672">=</span> list(map(int, input()<span style="color:#f92672">.</span>split()))
</span></span><span style="display:flex;"><span>weights <span style="color:#f92672">=</span> list(map(int, input()<span style="color:#f92672">.</span>split()))
</span></span></code></pre></div><p>It&rsquo;s worth noting that you can also use <code>for</code> loops, list comprehension, and <code>map</code> all together:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>output <span style="color:#f92672">=</span> sum([x[<span style="color:#ae81ff">0</span>] <span style="color:#f92672">*</span> x[<span style="color:#ae81ff">1</span>] <span style="color:#66d9ef">for</span> x <span style="color:#f92672">in</span> zip(values, weights)]) <span style="color:#f92672">/</span> sum(weights)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>print(round(output, <span style="color:#ae81ff">1</span>))
</span></span></code></pre></div><h2 id="your-iteration-toolbox">Your iteration toolbox</h2>
<p>Each of these methods of iteration in Python have a special place in the code I write every day. I hope these examples have helped you see how to use <code>for</code> loops, list comprehensions, and <code>map</code> in your own Python code!</p>
<p>If you like this post, there&rsquo;s a lot more where that came from! I write about efficient programming for coders and for leading technical teams. Check out the posts below!</p>
]]></content></entry><entry><title type="html">/site</title><link href="https://victoria.dev/site/"/><id>https://victoria.dev/site/</id><author><name>Victoria Drake</name></author><published>0001-01-01T00:00:00+00:00</published><updated>0001-01-01T00:00:00+00:00</updated><content type="html"><![CDATA[<h1 id="whats-all-this">What&rsquo;s All This?</h1>
<p>Welcome to <a href="https://victoria.dev">victoria.dev</a>, a personal website wholly created and owned by me, Victoria Drake. I&rsquo;ve produced everything you see on the site—from research and writing to illustrations, design, code, and deployment.</p>
<p>The site is <a href="https://github.com/victoriadrake/victoriadrake.github.io">open source</a>, so feel free to explore how it works!</p>
<h2 id="technical-features">Technical Features</h2>
<ul>
<li><strong>Static Site Generation</strong>: Built with <a href="https://gohugo.io/">Hugo</a> for speed and flexibility.</li>
<li><strong>Search Functionality</strong>: Implemented using <a href="https://lunrjs.com">Lunr.js</a>.</li>
<li><strong>Illustrations</strong>: I create all the illustrations and comics in my articles on my iPad.</li>
<li><strong>IndieWeb Integration</strong>: I&rsquo;ve implemented <a href="https://microformats.org/wiki/Main_Page">microformats2</a> markup, making the site compatible with social readers and other IndieWeb sites.</li>
</ul>
<h2 id="continuous-deployment">Continuous Deployment</h2>
<p>The site is automatically deployed with each update using GitHub Pages and GitHub Actions.</p>
<h2 id="development-tools">Development Tools</h2>
<ul>
<li><strong>Link Checking</strong>: I created <a href="https://github.com/victoriadrake/link-snitch">link-snitch</a>, a custom GitHub Action, to regularly check for broken links across the site. It&rsquo;s powered by <a href="https://github.com/victoriadrake/hydra-link-checker">Hydra</a>, a multithreaded Python site-crawling link checker built with the standard library.</li>
<li><strong>Code Quality</strong>: I use the <a href="https://pre-commit.com/">pre-commit framework</a> with <a href="https://github.com/DavidAnson/markdownlint-cli2">markdownlint-cli2</a> to maintain content quality.</li>
<li><strong>Self-Documenting Makefile</strong>: A <a href="https://victoria.dev/posts/how-to-create-a-self-documenting-makefile/">self-documenting Makefile</a> helps streamline development workflows without having to remember command-line flags.</li>
</ul>
<h2 id="contributions">Contributions</h2>
<p>If you find a mistake or bug, please open an issue so it can be fixed!</p>
<p>I don&rsquo;t accept guest blog posts or requests for placing links in posts.</p>
<h2 id="license">License</h2>
<p><a rel="license" href="http://creativecommons.org/licenses/by/4.0/"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by/4.0/88x31.png" /></a><br />
This work is licensed under a <a href="http://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 International License</a>.</p>
<hr>
<p><sub>There may or may not be secret pages and easter eggs on the site.</sub></p>

]]></content></entry><entry><title type="html">Hello! Your turn.</title><link href="https://victoria.dev/contact/"/><id>https://victoria.dev/contact/</id><author><name>Victoria Drake</name></author><published>0001-01-01T00:00:00+00:00</published><updated>0001-01-01T00:00:00+00:00</updated><content type="html"><![CDATA[<h2 id="email-me">Email me</h2>
<p>You can say <a href="mailto:hello@victoria.dev">hello@victoria.dev</a>.</p>
<p>Please note that I do not accept guest blog posts or requests for placing links in posts.</p>
<h2 id="find-me-in-the-verse">Find me in the &lsquo;verse</h2>
<ul>
<li><a href="https://x.com/victoriadotdev">x.com/victoriadotdev</a></li>
<li><a href="https://github.com/victoriadrake">github.com/victoriadrake</a></li>
<li><a href="https://medium.com/@victoriadotdev">medium.com/@victoriadotdev</a></li>
<li><a href="https://dev.to/victoria">dev.to/victoria</a></li>
</ul>
<p>You can also <a href="/site">learn how I built this site</a> or sign up for my newsletter below.</p>
<a href="https://medium.com/@victoriadotdev/subscribe" target="_blank" rel="noopener noreferrer" class="subscribe-button">
    <span class="subscribe-icon">📧</span>
    <span class="subscribe-text">Subscribe</span>
</a>

]]></content></entry><entry><title type="html">Victoria Drake</title><link href="https://victoria.dev/cv/"/><id>https://victoria.dev/cv/</id><author><name>Victoria Drake</name></author><published>0001-01-01T00:00:00+00:00</published><updated>0001-01-01T00:00:00+00:00</updated><content type="html"><![CDATA[<h2 id="engineering-director-and-principal-software-engineer">Engineering Director and Principal Software Engineer</h2>
<p>As a seasoned engineering leader, I guide teams to produce secure, industry-leading software for both government and private sector entities. My unique experience equips me to navigate the full spectrum of technology product development, from roadmap conception to final deployment and operations.</p>
<p>I&rsquo;ve excelled as an engineering director, principal software engineer, and mentor. I connect executive decisions with hands-on technical experience. I enjoy sharing my expertise at many levels, whether it&rsquo;s advising C-suite executives on technology strategy or software and security best practices, unblocking development teams, or deep-diving the codebase with developers to tackle complex architectural and technical challenges. I particularly thrive in leading distributed teams and bringing together complementary skills.</p>
<p>I&rsquo;m a co-author of the OWASP Web Security Testing Guide and advocate for cybersecurity training across organizations. Along with contributions to various business and engineering publications, my track record reflects my commitment to understanding the technology and security terrain and ensuring teams can traverse it with confidence.</p>
<p>You can read more <a href="/about">about me</a> or <a href="/contact">send me an email</a>.</p>
<p><a href="/Victoria-Drake-Resume.pdf">Download a PDF version</a> of my resume.</p>
<h2 id="principal-software-engineer">Principal Software Engineer</h2>
<p><em>September 2021 — September 2023</em></p>
<p>Principal Software Engineer for Sophos Factory, a modern DevSecOps automation pipeline builder.</p>
<ul>
<li>Enhanced platform scalability of a newly-acquired startup, aligning it with the broader business needs of Sophos through strategic technical and operational enhancements</li>
<li>Collaborated across teams to seamlessly integrate Sophos Factory with broader Sophos offerings and product strategy</li>
<li>Established standards and workflow processes to scale up the delivery of new features</li>
<li>Ensured roadmap planning and comprehensive feature requirements aligned with the broader company strategy and roadmap</li>
</ul>
<h2 id="director-of-engineering">Director Of Engineering</h2>
<p><em>March 2020 — September 2021</em></p>
<p>Directed software development at ZibaSec, a modern cybersecurity awareness training platform that uses realistic phishing simulations to create lasting behavior change and cyber risk reduction.</p>
<ul>
<li>Led the engineering group to design, implement, and secure a serverless cloud infrastructure while greatly improving application performance and achieving FedRAMP Authorization</li>
<li>Achieved 4.5x speedup in serverless application performance using multiple infrastructure components and distributed computing techniques</li>
<li>Created and implemented strategies for increasing knowledge transfer and organizational scalability in a growing, remote-first company</li>
<li>Reduced onboarding time for new engineers by 75% by leading an overhaul of onboarding processes and documentation</li>
<li>Scaled the engineering team size by 3x through improved processes for recruiting, interviewing, and hiring</li>
</ul>
<p>Related:</p>
<ul>
<li><a href="https://github.com/customer-stories/zibasec">ZibaSec &amp; GitHub: Would you volunteer your company for a cyber attack?</a></li>
<li><a href="https://web.archive.org/web/20210727210401/https://threat.technology/zibasecs-phishtaco-platform-achieves-fedramp-moderate-authorization/">ZibaSec’s PhishTACO Platform achieves FedRAMP Authorization</a></li>
</ul>
<h2 id="owasp-core-contributor-team">OWASP Core Contributor Team</h2>
<p><em>August 2019 — present</em></p>
<p>Co-author and core maintainer for the OWASP WSTG. The Open Web Application Security Project (OWASP) Web Security Testing Guide (WSTG) is the foremost open source resource for testing web application security.</p>
<ul>
<li>Built and established modern CI/CD and automation practices</li>
<li>Serve as technical editor for submissions from contributors</li>
</ul>
<p>Related:</p>
<ul>
<li><a href="https://owasp.org/2020/12/03/wstg-v42-released.html">OWASP Web Security Testing Guide v4.2 released</a></li>
</ul>
<h2 id="freecodecamp-coding-mentor-contributor">freeCodeCamp Coding Mentor, Contributor</h2>
<p><em>2017 — 2021</em></p>
<p>Recognized as a Top Contributor for three consecutive years at freeCodeCamp, a global 501(c)(3) non-profit organization that helps millions of people worldwide learn how to code.</p>
<ul>
<li>Served as organizer for the 2017 inaugural freeCodeConference in Toronto</li>
<li>Provided mentorship, code review, and career guidance to motivated technologists worldwide</li>
</ul>
<h2 id="senior-software-developer-consultant">Senior Software Developer, Consultant</h2>
<p><em>2016 — 2021</em></p>
<p>As a senior technology leader with a background in cybersecurity and full-stack software development, I provided executive leadership insights and technical guidance on product and process improvement.</p>
<p>Focus areas:</p>
<ul>
<li>Leader mentorship and development</li>
<li>Increasing development velocity in engineering teams</li>
<li>Application infrastructure and code efficiency, speedup, and cost savings</li>
</ul>
<p>Products and case studies:</p>
<ul>
<li>ApplyByAPI.com, SaaS that improves the technical hiring process by filtering candidates at the top of the funnel, and reduces human hours spent on screening</li>
<li>Modern e-commerce solutions for legacy industries, such as large-scale commercial building construction materials</li>
<li>Product design and product management for applications including an audio virtual reality application</li>
</ul>
<p>Related:</p>
<ul>
<li><a href="https://github.blog/2020-06-26-github-action-hero-victoria-drake/">GitHub Action Hero: Victoria Drake</a></li>
</ul>
<h2 id="education">Education</h2>
<p><strong>Master of Science, Computer Science</strong> - Georgia Institute of Technology</p>
<h2 id="contact">Contact</h2>
<p><a href="mailto:hello@victoria.dev">hello@victoria.dev</a></p>
]]></content></entry><entry><title type="html">Victoria&amp;#39;s Bookshelf</title><link href="https://victoria.dev/bookshelf/"/><id>https://victoria.dev/bookshelf/</id><author><name>Victoria Drake</name></author><published>0001-01-01T00:00:00+00:00</published><updated>0001-01-01T00:00:00+00:00</updated><content type="html"><![CDATA[<p>Books that have measurably contributed to my skill stack are shared here.</p>
<h2 id="required-reading-for-technology-leaders">Required reading for technology leaders</h2>
<div class="column book-list">
<div class="row book">
    <div class="card-img">
        <a href="https://amzn.to/36LNqy6">
            <img src="covers/extreme_ownership.jpeg" />
        </a>
    </div>
    <div class="card-content">
        <h3 class="card-title">
            <a href="https://amzn.to/36LNqy6">
                Extreme Ownership: How U.S. Navy SEALs Lead and Win
            </a>
        </h3>
        <p class="card-text"><em>Jocko Willink, Leif Babin</em></p>
        <p class="card-text">Foundational mindset and principles of leadership. How taking ownership of your work, project, and yourself helps to make you a better leader.</p>
    </div>
</div>

<div class="row book">
    <div class="card-img">
        <a href="https://amzn.to/2Ld6Gw6">
            <img src="covers/art_action.jpg" />
        </a>
    </div>
    <div class="card-content">
        <h3 class="card-title">
            <a href="https://amzn.to/2Ld6Gw6">
                The Art of Action: How Leaders Close the Gaps between Plans, Actions and Results
            </a>
        </h3>
        <p class="card-text"><em>Stephen Bungay</em></p>
        <p class="card-text">To understand how empowering your team to make decisions without you provides a significant competitive edge. An extremely worthwhile leadership curriculum. I&#39;d get the hardcover.</p>
    </div>
</div>

<div class="row book">
    <div class="card-img">
        <a href="https://amzn.to/2In8di4">
            <img src="covers/thinking_fast_slow.jpg" />
        </a>
    </div>
    <div class="card-content">
        <h3 class="card-title">
            <a href="https://amzn.to/2In8di4">
                Thinking, Fast and Slow
            </a>
        </h3>
        <p class="card-text"><em>Daniel Kahneman</em></p>
        <p class="card-text">To help gain a foundational understanding of how people think and react. Largely considered transformative in the field of cognitive psychology.</p>
    </div>
</div>

</div>
<h2 id="non-coding-books-for-coders">Non-coding books for coders</h2>
<p>A lot of non-technical knowledge gems can contribute to your programming skills! Here are the most helpful ones I&rsquo;ve read myself.</p>
<div class="column book-list">
<div class="row book">
    <div class="card-img">
        <a href="https://amzn.to/3qy81hc">
            <img src="covers/power_of_habit.jpg" />
        </a>
    </div>
    <div class="card-content">
        <h3 class="card-title">
            <a href="https://amzn.to/3qy81hc">
                The Power of Habit
            </a>
        </h3>
        <p class="card-text"><em>Charles Duhigg</em></p>
        <p class="card-text">To understand how to use your embedded superpowers to trick yourself into getting good at anything, including coding.</p>
    </div>
</div>

<div class="row book">
    <div class="card-img">
        <a href="https://amzn.to/36OsjLn">
            <img src="covers/thanks_feedback.jpg" />
        </a>
    </div>
    <div class="card-content">
        <h3 class="card-title">
            <a href="https://amzn.to/36OsjLn">
                Thanks for the Feedback: The Science and Art of Receiving Feedback Well
            </a>
        </h3>
        <p class="card-text"><em>Douglas Stone, Sheila Heen</em></p>
        <p class="card-text">How to responsibly review other people&#39;s work, and how to gratefully receive reviews of your own work.</p>
    </div>
</div>

<div class="row book">
    <div class="card-img">
        <a href="https://amzn.to/37JdxVF">
            <img src="covers/war_art.jpg" />
        </a>
    </div>
    <div class="card-content">
        <h3 class="card-title">
            <a href="https://amzn.to/37JdxVF">
                The War of Art
            </a>
        </h3>
        <p class="card-text"><em>Steven Pressfield</em></p>
        <p class="card-text">A guide to building systems that overcome inner resistance and support creative work, like programming.</p>
    </div>
</div>

<div class="row book">
    <div class="card-img">
        <a href="https://amzn.to/36Or5Qv">
            <img src="covers/grit.jpg" />
        </a>
    </div>
    <div class="card-content">
        <h3 class="card-title">
            <a href="https://amzn.to/36Or5Qv">
                Grit: The Power of Passion and Perseverance
            </a>
        </h3>
        <p class="card-text"><em>Angela Duckworth</em></p>
        <p class="card-text">To understand that struggling is an important part of the process, and the how to cultivate a mindset for dealing with it productively.</p>
    </div>
</div>

</div>
<div id="centered-footer">
<em>This page helps to support my work and contains Amazon affiliate links. If you happen to like a recommendation and make a purchase, I'll get a small thank-you commission at no extra cost to you!</em>
</div>
]]></content></entry></feed>