<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Cihat Gündüz</title>
    <description>The latest articles on DEV Community by Cihat Gündüz (@jeehut).</description>
    <link>https://dev.to/jeehut</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F282124%2F9c931bf4-164e-4152-a52b-0feb95859e7b.jpeg</url>
      <title>DEV Community: Cihat Gündüz</title>
      <link>https://dev.to/jeehut</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jeehut"/>
    <language>en</language>
    <item>
      <title>Laser Focus priority strategy</title>
      <dc:creator>Cihat Gündüz</dc:creator>
      <pubDate>Mon, 27 Sep 2021 14:03:47 +0000</pubDate>
      <link>https://dev.to/jeehut/laser-focus-priority-strategy-31ok</link>
      <guid>https://dev.to/jeehut/laser-focus-priority-strategy-31ok</guid>
      <description>&lt;p&gt;&lt;strong&gt;&lt;em&gt;Have you ever worked on a bigger change for an app and struggled with the release schedule? Have you ever wanted to get feedback from users early but weren't sure when exactly the right time is? Here's a simple but effective prioritization technique that can help slim down your scope and give you more confidence in it with different stages that can be mapped to Alpha, Beta &amp;amp; Release.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are lots of prioritization techniques that aim to solve different problems. You've probably already used some form of value vs. effort-based prioritization techniques, such as &lt;a href="https://www.productplan.com/glossary/rice-scoring-model/" rel="noopener noreferrer"&gt;RICE&lt;/a&gt;. Maybe you've even asked your target audience with a purposefully designed survey to learn from them, e.g. using the &lt;a href="https://en.wikipedia.org/wiki/Kano_model" rel="noopener noreferrer"&gt;KANO model&lt;/a&gt;. Every prioritization technique has its use cases and maybe they already helped you make a lot of useful decisions.&lt;/p&gt;

&lt;p&gt;But these strategies are designed for a &lt;strong&gt;higher-level&lt;/strong&gt; kind of &lt;strong&gt;prioritization&lt;/strong&gt;, as in deciding if you should be implementing feature A or feature B first or if feature C is even needed in the next version at all. They &lt;strong&gt;don't scale down&lt;/strong&gt; to tasks or even sub-tasks of your features though, so it's quite possible to do too much within a specific feature. Also, they don't help answer when you can start putting the feature in users' hands for early feedback to apply user-focused approaches, such as the &lt;a href="https://en.wikipedia.org/wiki/Lean_startup" rel="noopener noreferrer"&gt;Lean Startup&lt;/a&gt; methodology. One could of course opt for a method that is independent of scale, like the &lt;a href="https://en.wikipedia.org/wiki/MoSCoW_method" rel="noopener noreferrer"&gt;MoSCoW method&lt;/a&gt;, but their categories wouldn't be easy to rate because they're so abstract that different people would have different expectations for each category.&lt;/p&gt;

&lt;p&gt;The goal of the Laser Focus prioritization strategy I suggest in this article is to provide clear rating categories, help with task scopes and provide an easy to apply method for interpretation. All three aspects together help you stay laser-focused.&lt;/p&gt;

&lt;h2&gt;
  
  
  Laser Focus categories
&lt;/h2&gt;

&lt;p&gt;There are three goals we want to reach with our categories: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Decide which tasks are &lt;strong&gt;in the scope&lt;/strong&gt; of the currently planned release. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prioritize&lt;/strong&gt; tasks needed for an Alpha or Beta version &lt;strong&gt;higher&lt;/strong&gt; than the others.&lt;/li&gt;
&lt;li&gt;The Categories names should have an actionable, self-contained &lt;strong&gt;meaning&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We're suggesting the following categories which fulfill all requirements:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjv363gqbqzboan3ocvl1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjv363gqbqzboan3ocvl1.png" alt="VECtOR"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Vital&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Absolute minimum needed for the first round of testing. Can be ugly.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This allows for shipping a product with just "Vital" features or tasks implemented to a small group of testers to get feedback early. Of course, the scope of this Alpha testing should be made clear stating what basic features or tasks are still missing so they are not unnecessarily reported by the testers. But the vitals of the product or feature can be tested already and we get a first round of feedback if we're headed in the right direction.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Essential&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Core aspects required for basic functionality. Can have rough edges.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A second and bigger round of testing can be started as soon as all "Essential" features or tasks are implemented. At this level, no specific testing scope needs to be communicated, it should be enough to call the version a "Beta" version where the base features are available but still a lot of things are missing or incomplete.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Completing&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Ironing out rough-edges and completing aspects of functionality.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The "Completing" level defines the scope where the final product is ready to be released. In some situations, e.g. if a new version was announced for a specific date, the product can also be released while still, some "Completing" tasks are open, but then it should be publicly marked as "Beta". Typically this level includes all kinds of features or tasks that are important for a bigger customer base but are not relevant to evaluate the core of the product.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Optional&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Nice-to-haves that can be delayed to later (versions) or skipped entirely.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The "Optional" level has the notion that the features or tasks rated as such are wanted things, but that they are in no way necessary to release a finalized version of a product, even long term. Hence they can also be easily delayed or scrapped if needed as per the resources of the team.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Retracting&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Nice-to-haves (at first sight) that can (potentially) cause more harm than improve things.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Unlike "Optional", features or tasks rated as "Retracting" should be &lt;em&gt;actively avoided&lt;/em&gt;. That means it can make sense to document or keep them somewhere including the rationale why they should be avoided for long-term decision making. This saves time when the same idea comes up again sometime in the future. Also, if multiple people are involved in the rating, it can help identify the tasks where discussion might be necessary to clarify the effect of a task on the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Laser Focus matrix
&lt;/h2&gt;

&lt;p&gt;The second pillar of the Laser Focus strategy is its &lt;strong&gt;multi-dimensional scalability&lt;/strong&gt;. To explain what this means and why it is important, let's apply the categories we have so far with an example: Let's develop a stopwatch app to track time for different things done throughout a day. This is the initial list of feature ideas, rated using the Laser Focus categories:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create projects → &lt;strong&gt;Essential&lt;/strong&gt; (essential to app, but pre-filled projects enough for first test)&lt;/li&gt;
&lt;li&gt;Edit projects → &lt;strong&gt;Completing&lt;/strong&gt; (not a necessity for testing purposes, but for final release)&lt;/li&gt;
&lt;li&gt;Delete projects → &lt;strong&gt;Completing&lt;/strong&gt; (cleanup task, not needed for testing purposes, but for final)&lt;/li&gt;
&lt;li&gt;Start/Stop a timer → &lt;strong&gt;Vital&lt;/strong&gt; (core idea of app, vital part of the app)&lt;/li&gt;
&lt;li&gt;Select a project for the timer → &lt;strong&gt;Vital&lt;/strong&gt; (without selecting project, app idea not fulfilled)&lt;/li&gt;
&lt;li&gt;Edit past tracked times → &lt;strong&gt;Retracting&lt;/strong&gt; (V2 with competitive feature, risk of cheating)&lt;/li&gt;
&lt;li&gt;Delete past tracked times → &lt;strong&gt;Optional&lt;/strong&gt; (nice to have, no risk of cheating as no added time)&lt;/li&gt;
&lt;li&gt;Show historical time tracked on a selected project → &lt;strong&gt;Essential&lt;/strong&gt; (core use case for app)&lt;/li&gt;
&lt;li&gt;Show projects with the most tracked time → &lt;strong&gt;Essential&lt;/strong&gt; (core use case for app)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Thanks to the categorization, we can already exclude two features from the first release and recognized even a feature we should probably never implement (6) that should be permanently documented. But more importantly, we now know that 4 and 5 are the "Vital" features to implement first. Let's start working on their sub-tasks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start/Stop a timer → &lt;strong&gt;Vital&lt;/strong&gt;

&lt;ol&gt;
&lt;li&gt;Design Start/Stop button layout (low fidelity)&lt;/li&gt;
&lt;li&gt;Design Start/Stop button coloring &amp;amp; icons (high fidelity)&lt;/li&gt;
&lt;li&gt;Design Start/Stop button pulsating shadow effect (animations)&lt;/li&gt;
&lt;li&gt;Implement Start/Stop button layout (low fidelity)&lt;/li&gt;
&lt;li&gt;Implement Start/Stop button coloring &amp;amp; icons (high fidelity)&lt;/li&gt;
&lt;li&gt;Implement Start/Stop button pulsating shadow effect (animations)&lt;/li&gt;
&lt;li&gt;Setup basic tracked time database models&lt;/li&gt;
&lt;li&gt;Persist Start/Stop actions into the database&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;Select a project for the timer → &lt;strong&gt;Vital&lt;/strong&gt;

&lt;ol&gt;
&lt;li&gt;Design project selector navigation &amp;amp; layout (low fidelity)&lt;/li&gt;
&lt;li&gt;Design project selector shapes, colors &amp;amp; icons (high fidelity)&lt;/li&gt;
&lt;li&gt;Design project selector navigation &amp;amp; layout (low fidelity)&lt;/li&gt;
&lt;li&gt;Design project selector shapes, colors &amp;amp; icons (high fidelity)&lt;/li&gt;
&lt;li&gt;Persist selected project into the tracked time database model&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;/ol&gt;

&lt;p&gt;All clear, let's get started, right? &lt;em&gt;Right?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;No. I'm sure you noticed it already while reading/skimming through them. There's a problem. We have prioritized the features thinking about what's really necessary for being testable, for putting the app into users' hands. But now we have the same problem again, just on a different level. These tasks (and potentially also their sub-tasks) aren't all "Vital" for our very first version to put in users' hands. How can we fix this? Should we apply another rating for the tasks, too?&lt;/p&gt;

&lt;p&gt;Yes, absolutely! This is actually a requirement in the Laser Focus strategy: Apply the rating on all levels down the road! Not necessarily above levels, where you are allowed to choose any alternative prioritization technique. But the lower levels from wherever you want to start from should all be rated like this. Let's assign the Laser Focus categories to the tasks, too and then see what this means for overall priority:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start/Stop a timer → &lt;strong&gt;Vital&lt;/strong&gt;

&lt;ol&gt;
&lt;li&gt;Design Start/Stop button layout (low fidelity) → &lt;strong&gt;Vital&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Design Start/Stop button coloring &amp;amp; icons (high fidelity) → &lt;strong&gt;Completing&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Design Start/Stop button pulsating shadow effect (animations) → &lt;strong&gt;Optional&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Implement Start/Stop button layout (low fidelity) → &lt;strong&gt;Vital&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Implement Start/Stop button coloring &amp;amp; icons (high fidelity) → &lt;strong&gt;Completing&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Implement Start/Stop button pulsating shadow effect (animations) → &lt;strong&gt;Optional&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Setup basic tracked time database models → &lt;strong&gt;Essential&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Persist Start/Stop actions into database → &lt;strong&gt;Essential&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;Select a project for the timer → &lt;strong&gt;Vital&lt;/strong&gt;

&lt;ol&gt;
&lt;li&gt;Design project selector navigation &amp;amp; layout (low fidelity) → &lt;strong&gt;Vital&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Design project selector shapes, colors &amp;amp; icons (high fidelity) → &lt;strong&gt;Completing&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Implement project selector navigation &amp;amp; layout (low fidelity) → &lt;strong&gt;Vital&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Implement project selector shapes, colors &amp;amp; icons (high fidelity) → &lt;strong&gt;Completing&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Persist selected project into tracked time database model → &lt;strong&gt;Essential&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;/ol&gt;

&lt;p&gt;It's important to note that the reference value for the ratings of the tasks was the feature because it's the direct parent. This means that I asked myself the question "Is persisting Start/Stop actions into database vital or essential &lt;em&gt;to the feature&lt;/em&gt; Start/Stop a timer?" and not to the app or anything else. This makes answering the questions much easier.&lt;/p&gt;

&lt;p&gt;Let's visualize these two different levels of rating with a simple matrix. On the X-axis we put the ratings of the features. On the Y-axis the ratings of the tasks. The circles represent the tasks:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2lsufkxgoiiuwxicxlw5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2lsufkxgoiiuwxicxlw5.png" alt="Example Matrix"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;As you can see, tasks 4a, 4d, 5a, and 5c are in the bottom left field, the "Vital-Vital" field, or short "VV". The field's background is tinted red. It contains all tasks the focus should be on first. Once they're all implemented, the very first testing round can begin, the Alpha phase starts.&lt;/p&gt;

&lt;p&gt;The tasks 4g, 4h, and 5e in the yellow-tinted field "Vital-Essential" or short "VE" should be tackled next. Once all tasks in all three yellow-tinted fields are completed, the Beta phase starts.&lt;/p&gt;

&lt;p&gt;The "VC" field with its "Completing" tasks for the "Vital" features should be tackled last among the tasks we defined so far. Once all tasks in all green-tinted fields are done, it's Release time.&lt;/p&gt;

&lt;p&gt;In the above example, we skipped the tasks for all non-Vital features. If we had rated them also, the full matrix could have looked something like this, including also the "Retracting" rating:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwgxx6ldb4ff81zrht2md.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwgxx6ldb4ff81zrht2md.png" alt="General Matrix"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;We can see how the Alpha, Beta, and Release tasks are circularly layered around the origin point (bottom left corner), visually providing us a priority for each task based on its distance to the origin. This easily scales to a third axis if for example sub-tasks were added to each task. Formally speaking, this scales to any number of dimensions. To calculate the overall category of any given element, just look up all ancestors and just select the lowest priority as the overall category of the "atomic" (lowest level) element. For example, imagine a sub-task with the category rating "Essential", a parent task rated "Vital" and its parent feature rated "Completing". Overall, the lowest priority is "Completing", so this is the overall category of the sub-task.&lt;/p&gt;

&lt;p&gt;Calculating the overall category alone can lead to many tasks being on the same level, especially at "Completing" where we have 5 different fields. A way of prioritizing features or tasks within the same category is by calculating the average of its own category combined with that of all its ancestor's categories. To do this, let's assign each category a number (from 1 "Vital" to 5 "Retracting"), the lowest level (e.g. a sub-task) can then be represented by a tuple, e.g. &lt;code&gt;(2, 1, 3)&lt;/code&gt; in the above example. The average of these numbers is simply calculated by &lt;code&gt;(2 + 1 + 3) / 3 = 2.0&lt;/code&gt;. Another task with more ancestors and the same overall "Completing" category might be rated as &lt;code&gt;(3, 2, 3, 1)&lt;/code&gt; and therefore have an average of &lt;code&gt;(3 + 2 + 3 + 1) / 4 = 2.25&lt;/code&gt;, so it should be prioritized lower. The higher the overall average, the lower the priority – that makes a lot of sense as the average number roughly resembles the distance to the origin – the highest possible priority.&lt;/p&gt;

&lt;p&gt;But don't worry, you don't actually have to calculate these averages, there's a simpler way based on the matrix we've seen above with enough precision:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc648lfmlqhowe2430cfr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc648lfmlqhowe2430cfr.png" alt="Priority Order"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The above diagram shows in which order the fields should be tackled, based on origin distance. Note that there are two fields placed 2nd, 4th, and 5th each. For these fields, there's a choice to be made that can be different depending on the situation: Should we focus more on adding more features? Or should we focus more on improving the already started features? For a feature focus expansion first, you should continue in direction of the "Feature category", e.g. "EV" before "VE". For improving existing features first, it should be the other way around. &lt;/p&gt;

&lt;h2&gt;
  
  
  Laser Focus breakdown
&lt;/h2&gt;

&lt;p&gt;In the above section, we learned that categorization on multiple levels is key to the Laser Focus concept. If you try to apply this to your project right away, you may realize though that many or even all of your features or tasks are actually "Vital" or "Essential" to you. If this is the case, then it's a sign that you have probably not efficiently split your tasks yet.&lt;/p&gt;

&lt;p&gt;That's why it's important to break down your tasks the right way before categorizing them. The guiding question you should ask yourself while splitting features into tasks or tasks into sub-tasks should not be restricted to "which steps do I need to make to finalize it". You should also think about the effort for each step and if the effort isn't negligibly small, you might want to consider splitting it away. Sometimes it might seem to be hard doing that, but more often than not, it's a good idea to follow the approach "make it work, then make it better" while splitting the tasks.&lt;/p&gt;

&lt;p&gt;For example, for the above feature "Start/Stop a timer" we could have split it up into 3 tasks: "Design the Start/Stop buttons", "Implement the Start/Stop buttons" and "Persist data". The problem with this is that there are no different levels of completion. It's better to break it down even further. Of course, we could do that as sub-tasks under these tasks, but to make priority calculation easier, it is recommended to do it in fewer levels. So instead we opted for "Design the Start/Stop button &lt;em&gt;layout&lt;/em&gt;", "Implement Start/Stop button &lt;em&gt;layout&lt;/em&gt;" and the same two tasks also for "... coloring &amp;amp; icons" and "... pulsating shadow effect".&lt;/p&gt;

&lt;p&gt;Ask yourself which parts have their own effort and split them so each task is worth being prioritized based on the effort needed. Don't split micro-tasks away, it's not worth prioritizing such small tasks, just keep them as part of another task.&lt;/p&gt;

&lt;p&gt;A proper breakdown is very important for the Laser Focus strategy to be effective.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Let's sum up the Laser Focus prioritization strategy:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Break down&lt;/strong&gt; your features and tasks into smaller steps of different completion levels&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate them&lt;/strong&gt; on each level with "Vital", "Essential", "Completing", "Optional" or "Retracting"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visualize or calculate&lt;/strong&gt; the overall priority for the lowest level by considering all ancestors&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Apply these steps at any given time for your project and it will help you keep focusing on the important things and confidently putting your work-in-progress versions into users' hands early.&lt;/p&gt;

&lt;p&gt;I hope this helps!&lt;/p&gt;

&lt;p&gt;_This article was written &lt;strong&gt;by &lt;a href="https://twitter.com/Jeehut" rel="noopener noreferrer"&gt;Cihat Gündüz&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>management</category>
      <category>prioritization</category>
      <category>efficiency</category>
      <category>focus</category>
    </item>
    <item>
      <title>Git Merge vs Rebase</title>
      <dc:creator>Cihat Gündüz</dc:creator>
      <pubDate>Thu, 01 Jul 2021 13:22:23 +0000</pubDate>
      <link>https://dev.to/jeehut/git-merge-vs-rebase-5afh</link>
      <guid>https://dev.to/jeehut/git-merge-vs-rebase-5afh</guid>
      <description>&lt;p&gt;&lt;strong&gt;An FAQ that explains and answers when to use which and why.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There's a common discussion among developers about how teams should use &lt;a href="https://git-scm.com/" rel="noopener noreferrer"&gt;Git&lt;/a&gt; to make sure everyone is always up-to-date with the latest changes in the &lt;code&gt;main&lt;/code&gt; branch. The typical situation this question arises is when someone worked on a new branch and then once the work is done and ready to be merged, the main branch had changes in the meantime in a way that the work branch is outdated and now has &lt;strong&gt;merge conflicts&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Obviously, they need to be resolved before the work branch can be merged. But the question is: &lt;em&gt;How&lt;/em&gt; should this situation be resolved? Should we &lt;em&gt;&lt;strong&gt;merge&lt;/strong&gt;&lt;/em&gt; the &lt;code&gt;main&lt;/code&gt; branch into the work branch? Or should we &lt;em&gt;&lt;strong&gt;rebase&lt;/strong&gt;&lt;/em&gt; the work branch onto the latest &lt;code&gt;main&lt;/code&gt; branch?&lt;/p&gt;

&lt;p&gt;In my opinion, there's only one correct answer to this question. From my experience, the main reason why so many discussions arise around this topic is that there's a lot of misunderstandings out there about how &lt;code&gt;merge&lt;/code&gt; and &lt;code&gt;rebase&lt;/code&gt; differ from each other in this context and a general lack of understanding, what a &lt;code&gt;rebase&lt;/code&gt; even is.&lt;/p&gt;

&lt;p&gt;So I created an FAQ for my team which tries to clarify things. Let me share:&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a &lt;code&gt;merge&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;A commit, that combines all changes of a different branch into the current.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a &lt;code&gt;rebase&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;Re-comitting all commits of the current branch onto a different base commit.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are the main differences between &lt;code&gt;merge&lt;/code&gt; and &lt;code&gt;rebase&lt;/code&gt;?
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;merge&lt;/code&gt; executes only &lt;strong&gt;one&lt;/strong&gt; new commit. &lt;code&gt;rebase&lt;/code&gt; typically executes &lt;strong&gt;multiple&lt;/strong&gt; (number of commits in current branch).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;merge&lt;/code&gt; produces a &lt;strong&gt;new&lt;/strong&gt; generated commit (the so called merge-commit). &lt;code&gt;rebase&lt;/code&gt; only moves &lt;strong&gt;existing&lt;/strong&gt; commits.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  In which situations should we use a &lt;code&gt;merge&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;merge&lt;/code&gt; whenever you want to add changes of a branched out branch &lt;strong&gt;back&lt;/strong&gt; into the base branch.&lt;/p&gt;

&lt;p&gt;Typically, you do this by clicking the "Merge" button on Pull/Merge Requests, e.g. on GitHub.&lt;/p&gt;

&lt;h3&gt;
  
  
  In which situations should we use a &lt;code&gt;rebase&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;rebase&lt;/code&gt; whenever you want to add &lt;strong&gt;changes of a base branch&lt;/strong&gt; back to a branched out branch.&lt;/p&gt;

&lt;p&gt;Typically, you do this in &lt;code&gt;work&lt;/code&gt; branches whenever there's a change in the &lt;code&gt;main&lt;/code&gt; branch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why not use &lt;code&gt;merge&lt;/code&gt; to merge changes from the base branch into a work branch?
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;The git history will include many &lt;strong&gt;unnecessary merge commits&lt;/strong&gt;. If multiple merges were needed in a work branch, then the work branch might even hold more merge commits than actual commits!&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This creates a loop which &lt;strong&gt;destroys the mental model that Git was designed by&lt;/strong&gt; which causes troubles in any visualization of the Git history.&lt;/p&gt;

&lt;p&gt;Imagine there's a river (e.g. the "Nile"). Water is flowing in one direction (direction of time in Git history). Now and then, imagine there's a branch to that river and suppose most of those branches merge back into the river. That's what the flow of a river might look like naturally. It makes sense.&lt;/p&gt;

&lt;p&gt;But then imagine there's a small branch of that river. Then, for some reason, &lt;strong&gt;the river merges into the branch&lt;/strong&gt; and the branch continues from there. The river has now technically disappeared, it's now in the branch. But then, somehow magically, that branch is merged back into the river. Which river you ask? I don't know. The river should actually be in the branch now, but somehow it still continues to exist and I can merge the branch back into the river. So, the river is in the river. Kind of doesn't make sense.&lt;/p&gt;

&lt;p&gt;This is exactly what happens when you &lt;code&gt;merge&lt;/code&gt; the base branch into a &lt;code&gt;work&lt;/code&gt; branch and then when the &lt;code&gt;work&lt;/code&gt; branch is done, you merge that back into the base branch again. The mental model is broken. And because of that, you end up with a branch visualization that's not very helpful.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Example Git History when using &lt;code&gt;merge&lt;/code&gt;:
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://i.stack.imgur.com/Oqqmm.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.stack.imgur.com%2FOqqmm.png" alt="Example Git History when using  raw `merge` endraw "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note the many commits starting with &lt;code&gt;Merge branch 'main' into ...&lt;/code&gt; (marked with yellow boxes). They don't even exist if you rebase (there, you will only have pull request merge commits). Also note the many visual branch merge loops (&lt;code&gt;main&lt;/code&gt; into &lt;code&gt;work&lt;/code&gt; into &lt;code&gt;main&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Example Git History when using &lt;code&gt;rebase&lt;/code&gt;:
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://i.stack.imgur.com/0ZVla.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.stack.imgur.com%2F0ZVla.png" alt="Example Git History when using  raw `rebase` endraw "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Much cleaner Git history with much less merge commits and no cluttered visual branch merge loops whatsoever.&lt;/p&gt;

&lt;h3&gt;
  
  
  Are there any downsides / pitfalls with &lt;code&gt;rebase&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;Yes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Because a &lt;code&gt;rebase&lt;/code&gt; moves commits (technically re-executes them), the commit date of all moved commits will be the time of the rebase and the &lt;strong&gt;git history loses the initial commit time&lt;/strong&gt;. So, if the exact date of a commit is needed for some reason, then &lt;code&gt;merge&lt;/code&gt; is the better option. But typically, a clean git history is much more useful than exact commit dates.&lt;/li&gt;
&lt;li&gt;If the rebased branch has multiple commits that change the same line and that line was also changed in the base branch, you might need to solve merge conflicts for that same line multiple times, which you never need to do when merging. So, on average, there's more merge conflicts to solve.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Tips to reduce merge conflicts when using &lt;code&gt;rebase&lt;/code&gt;:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Rebase often&lt;/strong&gt;. I typically recommend doing it at least once a day.&lt;/li&gt;
&lt;li&gt;Try to &lt;strong&gt;squash changes&lt;/strong&gt; on the same line into one commit as much as possible.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I hope this FAQ helps some teams out there. Like ❤️ it if you liked it! 😉&lt;/p&gt;

&lt;p&gt;_This article was written &lt;strong&gt;by &lt;a href="https://twitter.com/Jeehut" rel="noopener noreferrer"&gt;Cihat Gündüz&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>git</category>
      <category>merge</category>
      <category>rebase</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Primer on Regexes</title>
      <dc:creator>Cihat Gündüz</dc:creator>
      <pubDate>Thu, 06 May 2021 10:28:16 +0000</pubDate>
      <link>https://dev.to/jeehut/primer-on-regexes-3ke7</link>
      <guid>https://dev.to/jeehut/primer-on-regexes-3ke7</guid>
      <description>&lt;p&gt;In this post, I will try to give you a practical overview of Regular Expressions to teach you what they are, what they can be used for and a quick intro to how you can use them.&lt;/p&gt;

&lt;h1&gt;
  
  
  What are Regular Expressions even?
&lt;/h1&gt;

&lt;p&gt;Regular Expressions (short Regexes) are Strings that work as a DSL (domain-specific language) to do some common tasks within other Strings. A DSL can also be subscribed as "a programming language within a programming language".&lt;/p&gt;

&lt;p&gt;In the case of Regexes, the outer programming language can be any programming language that supports the &lt;code&gt;String&lt;/code&gt; type, it just has to support Regexes. Nearly all popular programming languages support Regexes, which makes Regexes so useful to know. The inner language of Regexes consists of only &lt;code&gt;String&lt;/code&gt; with some characters having a special meaning.&lt;/p&gt;

&lt;p&gt;For example in the String &lt;code&gt;".*@.*\.com"&lt;/code&gt; the &lt;code&gt;.&lt;/code&gt; means "any character", the &lt;code&gt;*&lt;/code&gt; means "any amount of &amp;lt;whatever precedes&amp;gt;", together &lt;code&gt;.*&lt;/code&gt; means "any amount of any character". Then we have a non-special character &lt;code&gt;@&lt;/code&gt;, then again &lt;code&gt;.*&lt;/code&gt; followed by &lt;code&gt;\&lt;/code&gt; which means "escape the next character and treat like a non-special character" so &lt;code&gt;\.&lt;/code&gt; together reads like a normal &lt;code&gt;.&lt;/code&gt; character without the special meaning "any character". Lastly, there's &lt;code&gt;com&lt;/code&gt; which is just a set of characters without any special meaning. Overall this Regex is a simple matcher for any email address ending with &lt;code&gt;.com&lt;/code&gt; and containing a &lt;code&gt;@&lt;/code&gt; somewhere.&lt;/p&gt;

&lt;h1&gt;
  
  
  What can I do with the "Regex DSL"?
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; With &lt;a href="https://www.ruby-lang.org/en/"&gt;Ruby&lt;/a&gt; installed (on Macs it's preinstalled), you can type &lt;code&gt;irb&lt;/code&gt; to start an interactive Ruby shell to play around with the samples below.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are three main functions that any Regex string can be used with:&lt;/p&gt;

&lt;h2&gt;
  
  
  1. &lt;code&gt;matches&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Given a Regex and another String, this function checks if the given String "matches" the Regex. This means if there's "any" part within the given String that matches the specified Regex, it returns &lt;code&gt;true&lt;/code&gt;, otherwise, it's &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example in Ruby (where &lt;code&gt;matches&lt;/code&gt; is called &lt;code&gt;match?&lt;/code&gt; – &lt;code&gt;?&lt;/code&gt; is part of the function name):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="sr"&gt;/.*@.*\.com/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'harry.potter@hogwarts.co.uk'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; false&lt;/span&gt;
&lt;span class="sr"&gt;/.*@.*\.com/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'queenie.goldstein@ilvermorny.com'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. &lt;code&gt;captures&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Given a Regex and another String, this function can read substrings out of the given text which matches marked portions of the given Regex. The portions in the Regex can be marked via &lt;code&gt;(&lt;/code&gt; and &lt;code&gt;)&lt;/code&gt;. They are called "capture groups".&lt;/p&gt;

&lt;p&gt;For example in Ruby (where &lt;code&gt;captures&lt;/code&gt; can be accessed on the &lt;code&gt;Match&lt;/code&gt; object):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="sr"&gt;/(.*)@(.*)\.com/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'queenie.goldstein@ilvermorny.com'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;captures&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; ["queenie.goldstein", "ilvermorny"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. &lt;code&gt;replace&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Given a Regex and a Template String, this function can automatically replace matches with a given String where even the capture groups can be referenced via &lt;code&gt;$1&lt;/code&gt;, &lt;code&gt;$2&lt;/code&gt; or in some languages also &lt;code&gt;\1&lt;/code&gt; , &lt;code&gt;\2&lt;/code&gt;, etc.&lt;/p&gt;

&lt;p&gt;For example in Ruby (where &lt;code&gt;replace&lt;/code&gt; is called &lt;code&gt;gsub&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;    &lt;span class="s1"&gt;'queenie.goldstein@ilvermorny.com'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/(.*)@(.*)\.com/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'\1@\2.org'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# =&amp;gt; "queenie.goldstein@ilvermorny.org"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  What does the "Regex DSL" look like?
&lt;/h1&gt;

&lt;p&gt;There's plenty of useful "cheat sheets" for this with great examples:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rexegg.com/regex-quickstart.html#chars"&gt;https://www.rexegg.com/regex-quickstart.html#chars&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.regular-expressions.info/examples.html"&gt;Regular Expression Examples&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Generally, there's 5 different kinds of DSL components to understand:&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Character/Group Modifiers (e.g. &lt;code&gt;*&lt;/code&gt;, &lt;code&gt;+&lt;/code&gt;, &lt;code&gt;{,}&lt;/code&gt;, &lt;code&gt;?&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;The default "building" block of Regexes are characters. After each character, you can write a modifier that tells how many times the preceding character is matched. The following modifiers are available:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;0&lt;/code&gt; or &lt;code&gt;1&lt;/code&gt; times:&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;?&lt;/code&gt; (example: &lt;code&gt;a?b?c?&lt;/code&gt; matches all of &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;ab&lt;/code&gt;, &lt;code&gt;abc&lt;/code&gt;, &lt;code&gt;bc&lt;/code&gt;, &lt;code&gt;c&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;1&lt;/code&gt; time exactly:&lt;/strong&gt;&lt;br&gt;
No modifier (default)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;0&lt;/code&gt; to ♾️ times:&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;*&lt;/code&gt; (example: &lt;code&gt;a*bc&lt;/code&gt; matches &lt;code&gt;bc&lt;/code&gt;, &lt;code&gt;abc&lt;/code&gt;, &lt;code&gt;aaabc&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;1&lt;/code&gt; to ♾️ times:&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;+&lt;/code&gt; (example: &lt;code&gt;a+bc&lt;/code&gt; matches &lt;code&gt;abc&lt;/code&gt;, &lt;code&gt;aaabc&lt;/code&gt; but not &lt;code&gt;bc&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;X&lt;/code&gt; times exactly:&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;{X}&lt;/code&gt; (example: &lt;code&gt;a{3}bc&lt;/code&gt; matches &lt;code&gt;aaabc&lt;/code&gt; but not &lt;code&gt;aabc&lt;/code&gt;, &lt;code&gt;aaaabc&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;X&lt;/code&gt; to &lt;code&gt;Y&lt;/code&gt; times:&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;{X,Y}&lt;/code&gt; (example: &lt;code&gt;a{2,5}bc&lt;/code&gt; matches &lt;code&gt;aaaaabc&lt;/code&gt;, but not &lt;code&gt;abc&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;X&lt;/code&gt; to ♾️ times:&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;{X,}&lt;/code&gt; (example: &lt;code&gt;a{2,}bc&lt;/code&gt; matches &lt;code&gt;aaaaaaaabc&lt;/code&gt; but not &lt;code&gt;abc&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;The same modifiers also work on Groups (e.g. &lt;code&gt;(abc)+&lt;/code&gt;) (see below for groups).&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Custom Sets (created with &lt;code&gt;[&lt;/code&gt; and &lt;code&gt;]&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;You can define custom sets of characters by listing them without any separator within brackets, e.g. for a set of the characters a, b, c and numbers 1, 2, 3 we would write &lt;code&gt;[abc123]&lt;/code&gt;. This is then considered as "one character of this set", thus matching multiple of them need character modifiers as in &lt;code&gt;[abc123]*&lt;/code&gt; or &lt;code&gt;[abc123]{2,5}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can also use &lt;code&gt;^&lt;/code&gt; at the beginning of a custom set to specify that you accept any character except the set you specified in the brackets, e.g. &lt;code&gt;[^\n]&lt;/code&gt; to accept any character except a newline.&lt;/p&gt;

&lt;p&gt;Characters of which you know are ordered right after each other like numbers or the Alphabet you can also use ranges by putting a &lt;code&gt;-&lt;/code&gt; in between, e.g. &lt;code&gt;[a-zA-Z0-9]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;[abc123]{3,}&lt;/code&gt; would not match &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;b&lt;/code&gt;, &lt;code&gt;c&lt;/code&gt;, &lt;code&gt;ab&lt;/code&gt;, but would match &lt;code&gt;111&lt;/code&gt;, &lt;code&gt;abc&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Predefined Sets (&lt;code&gt;\s&lt;/code&gt;, &lt;code&gt;\S&lt;/code&gt;, &lt;code&gt;\d&lt;/code&gt;, &lt;code&gt;\D&lt;/code&gt;, &lt;code&gt;\w&lt;/code&gt;, &lt;code&gt;\W&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;The following sets (simplified) are already pre-defined and can be used directly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;\s&lt;/code&gt; effectively same as &lt;code&gt;[ \t\n]&lt;/code&gt;, reads "any whitespace character"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;\S&lt;/code&gt; effectively same as &lt;code&gt;[^ \t\n]&lt;/code&gt;, reads "any non-whitespace character"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;\d&lt;/code&gt; effectively same as &lt;code&gt;[0-9]&lt;/code&gt;, reads "any digit"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;\D&lt;/code&gt; effectively same as &lt;code&gt;[^0-9]&lt;/code&gt;, reads "any non-digit"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;\w&lt;/code&gt; similar to &lt;code&gt;[a-zA-Z_0-9]&lt;/code&gt; (includes Umlauts etc.), reads "any word character"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;\W&lt;/code&gt; similar to &lt;code&gt;[^a-zA-Z_0-9]&lt;/code&gt; reads "any non-word character"&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Groups (e.g. &lt;code&gt;(&lt;/code&gt; and &lt;code&gt;)&lt;/code&gt;, &lt;code&gt;(?&amp;lt;name&amp;gt;&lt;/code&gt; and &lt;code&gt;)&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;Groups could be thought of like "words" or "sentences", they  change the default building block, which is "character" for any modifier to a set of characters, or a "group". For example, writing &lt;code&gt;abc*&lt;/code&gt; reads "one time a, one times b and any number of times c". If you want to write "any number of times abc" you do this: &lt;code&gt;(abc)*&lt;/code&gt;. The &lt;code&gt;abc&lt;/code&gt; is then considered one group and the regex would match the whole string &lt;code&gt;abcabcabc&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Groups also allow for specifying different options to choose from. For this, you write a group and separate the different words via a &lt;code&gt;|&lt;/code&gt; like so: &lt;code&gt;(abc|def)&lt;/code&gt; – this reads "either abc or def" and would match both &lt;code&gt;123abc123&lt;/code&gt; and &lt;code&gt;456def456&lt;/code&gt; but not &lt;code&gt;adbecf&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;These capture some sub-portions of a Regex and assign them a number or name which then can be used to reference them in code or in replacement template Strings. Typically capture groups like in &lt;code&gt;(.*)@(.*).com&lt;/code&gt; are used then referenced back via &lt;code&gt;\1@\2.com&lt;/code&gt; or &lt;code&gt;$1@$2.com&lt;/code&gt; (depending on the language).&lt;/p&gt;

&lt;p&gt;It's also possible to give the groups names, e.g. &lt;code&gt;(?&amp;lt;user&amp;gt;.*)@(?&amp;lt;domain&amp;gt;.*).com&lt;/code&gt; to reference back like in &lt;code&gt;${user}@${domain}.com&lt;/code&gt;, but these are advanced features which are implemented differently in different languages (and are missing in some).&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Match Modifiers (&lt;code&gt;\A&lt;/code&gt;, &lt;code&gt;\z&lt;/code&gt;, &lt;code&gt;^&lt;/code&gt;, &lt;code&gt;$&lt;/code&gt;, Lookarounds)
&lt;/h2&gt;

&lt;p&gt;By default, a match for a Regex, like &lt;code&gt;abc&lt;/code&gt; is done like a &lt;code&gt;contains&lt;/code&gt; method. But you can also specify that the &lt;code&gt;abc&lt;/code&gt; string needs to be at the beginning or end of a given string or of a line. For example, the &lt;code&gt;^&lt;/code&gt; in &lt;code&gt;^abc&lt;/code&gt; makes sure only strings with &lt;code&gt;abc&lt;/code&gt; at the beginning of a new line match. This will match &lt;code&gt;def\nabc&lt;/code&gt; but not &lt;code&gt;defabc&lt;/code&gt;. The &lt;code&gt;$&lt;/code&gt; in &lt;code&gt;abc$&lt;/code&gt; makes sure there's a line-end after &lt;code&gt;abc&lt;/code&gt;. Use &lt;code&gt;\A&lt;/code&gt; and &lt;code&gt;\z&lt;/code&gt; to match among the entire String (matching multiple lines). &lt;/p&gt;

&lt;p&gt;Lookaheads &amp;amp; Lookbehinds are more of an advanced topic and useful mostly when you want to match that the beginning or end of your Regex does NOT match a given Regex. In most cases, Regexes with Lookaheads &amp;amp; Lookbehinds can be rewritten with Capture Groups, so you should try to write them as Capture Groups instead and only read about these if the other options don't work as Lookaround are CPU-intensive operations and also kind of restricted (e.g. they don't support most modifiers).&lt;/p&gt;

&lt;p&gt;Here's a good place to learn about them:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.regular-expressions.info/lookaround.html"&gt;https://www.regular-expressions.info/lookaround.html&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Common Quirks and validating new Regexes
&lt;/h1&gt;

&lt;p&gt;One common thing to consider is that &lt;code&gt;.&lt;/code&gt; in most languages does &lt;strong&gt;not match the newline&lt;/strong&gt; character by default. But it can typically be turned on with an option, in Ruby by specifying the &lt;code&gt;/m&lt;/code&gt; at the end which stands for "make dot match newlines".&lt;/p&gt;

&lt;p&gt;Also note that in every language there are different characters that are &lt;strong&gt;reserved&lt;/strong&gt; due to how Strings work in them, for example in Ruby &lt;code&gt;/&lt;/code&gt; needs to be escaped with &lt;code&gt;\/&lt;/code&gt;, in Swift this escape is not needed but there you need to escape &lt;code&gt;{&lt;/code&gt; and &lt;code&gt;}&lt;/code&gt; with &lt;code&gt;\{&lt;/code&gt; and &lt;code&gt;\}&lt;/code&gt;. These quirks are important to remember when copy &amp;amp; pasting Regexes written for other languages.&lt;/p&gt;

&lt;p&gt;Generally, when writing a new Regex, I recommend using a website or tool with 3 features:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An option to add a sample String to match against.&lt;/li&gt;
&lt;li&gt;A Regex cheat sheet visible right on the screen to look up things.&lt;/li&gt;
&lt;li&gt;A live matcher for the regex you write among the given sample String.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The site I've come to use here is this (runs Ruby in the background) :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://rubular.com/"&gt;Rubular&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  How can I use Regexes in my projects &lt;em&gt;today&lt;/em&gt;?
&lt;/h1&gt;

&lt;p&gt;There's no need to wait until there's a good opportunity to use Regexes, you can simply lint your projects using Regular expressions (including Auto-Correction support) via AnyLint:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Flinesoft/AnyLint"&gt;Flinesoft/AnyLint&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;_This article was written &lt;strong&gt;by &lt;a href="https://twitter.com/Jeehut"&gt;Cihat Gündüz&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Photo by &lt;a href="https://unsplash.com/@olloweb"&gt;Agence Olloweb&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>regex</category>
      <category>dsl</category>
      <category>beginners</category>
      <category>programming</category>
    </item>
    <item>
      <title>Safer Localization in SwiftUI</title>
      <dc:creator>Cihat Gündüz</dc:creator>
      <pubDate>Sun, 19 Jul 2020 17:25:41 +0000</pubDate>
      <link>https://dev.to/jeehut/safer-localization-in-swiftui-4gn8</link>
      <guid>https://dev.to/jeehut/safer-localization-in-swiftui-4gn8</guid>
      <description>&lt;p&gt;Now that we've seen many improvements to SwiftUI during WWDC this year, including a possibility to go full-SwiftUI even on &lt;code&gt;App&lt;/code&gt; level (I consider &lt;code&gt;AppDelegate&lt;/code&gt; deprecated now), I just started working on my first serious pure SwiftUI-driven app. &lt;/p&gt;

&lt;p&gt;While most of the UI code I wrote was a lot of fun so far and I'm learning some tricks to deal with data management along the way, what I was very curious to see was if and how SwiftUI changes &amp;amp; improves upon the localization APIs as well. The examples I had seen so far from Apple and all the popular posts and guides were not really going into any detail there and it just looked like localization is somehow magically baked into the SwiftUI views.&lt;/p&gt;

&lt;p&gt;And that actually turned out to be the case, at least simply specifying a view like &lt;code&gt;Text("E-Mail")&lt;/code&gt; and putting the key &lt;code&gt;"E-Mail"&lt;/code&gt; into the &lt;code&gt;Localizable.strings&lt;/code&gt; file + providing different translations for other languages seems to just work.&lt;/p&gt;

&lt;p&gt;Digging a little further into how this works, I quickly came across the type providing the magic here: &lt;code&gt;LocalizedStringKey&lt;/code&gt;. SwiftUI views like &lt;code&gt;Text&lt;/code&gt; accept instances of this type as their first parameter and because &lt;code&gt;LocalizedStringKey&lt;/code&gt; conforms to the &lt;code&gt;ExpressibleByStringLiteral&lt;/code&gt; protocol, one can use String literals like &lt;code&gt;"E-Mail"&lt;/code&gt; in my example above and they are turned into instances of the &lt;code&gt;LocalizedStringKey&lt;/code&gt; struct automatically.&lt;/p&gt;

&lt;p&gt;Having a look into what other protocols the &lt;code&gt;LocalizedStringKey&lt;/code&gt; conforms to, I found &lt;code&gt;ExpressibleByStringInterpolation&lt;/code&gt; which is a protocol that was introduced in its new form with Swift 5 via &lt;a href="https://github.com/apple/swift-evolution/blob/master/proposals/0228-fix-expressiblebystringinterpolation.md"&gt;SE-228&lt;/a&gt;. Until I saw how Apple used this protocol for localization, I did not know about its existence at all, but of course there was already &lt;a href="https://nshipster.com/expressiblebystringinterpolation/"&gt;an NSHipster article&lt;/a&gt; about it, which only undermines it's usefulness. Here's an example what it looks like on the usage side:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello, &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; "Hello, %@"&lt;/span&gt;
&lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Last updated &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;formatter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dateFormatter&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; "Last updated %@"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While all these things make the new APIs much more readable and easier to use than the old &lt;code&gt;NSLocalizedString&lt;/code&gt; APIs, they do share some of their biggest flaws:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;There are &lt;strong&gt;no compiler checks&lt;/strong&gt; to ensure a key is (still) available in localization files&lt;/li&gt;
&lt;li&gt;There is no support whatsoever to keep keys &lt;strong&gt;consistent across languages&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;There is &lt;strong&gt;no autocompletion&lt;/strong&gt;, leading to many misspellings &amp;amp; exact name lookups&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I had already written about this in detail in my blog post &lt;a href="https://medium.com/@Jeehut/localization-in-swift-like-a-pro-48164203afe2"&gt;Localization in Swift like a Pro&lt;/a&gt; last year, where I also presented a solution for these problems using my tool &lt;a href="https://github.com/Flinesoft/BartyCrouch"&gt;BartyCrouch&lt;/a&gt; which I had started back in 2016 and the amazing &lt;a href="https://github.com/SwiftGen/SwiftGen"&gt;SwiftGen&lt;/a&gt;, which I can only recommend for any serious app project.&lt;/p&gt;

&lt;p&gt;But the workflow was designed for Interface Builder and &lt;code&gt;NSLocalizedString&lt;/code&gt; and while it does continue to work because you could simply pass &lt;code&gt;NSLocalizedString&lt;/code&gt; objects into SwiftUI views (they also accept plain &lt;code&gt;String&lt;/code&gt;s), it didn't feel natural to the new way of things in SwiftUI, so I've started exploring new approaches here.&lt;/p&gt;

&lt;p&gt;First, I checked if there's already a new approach by the SwiftGen contributors and found that there was already &lt;a href="https://github.com/SwiftGen/SwiftGen/issues/685"&gt;some discussion going on&lt;/a&gt; regarding the new &lt;code&gt;LocalizedStringKey&lt;/code&gt; struct. But things have not settled yet and it will probably take a lot more consideration and experience to get the localization part right. I decided to skip the "no autocompletion" issue for now as that can only be provided by generated code.&lt;/p&gt;

&lt;p&gt;But I tried to find a new way to improve on the other two issues and could quickly find a fix for the "consistency across languages" one as for that I could simply use some lesser known features of BartyCrouch:&lt;/p&gt;

&lt;p&gt;I use this &lt;code&gt;.bartycrouch.toml&lt;/code&gt; configuration file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[update]&lt;/span&gt;
&lt;span class="py"&gt;tasks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;["normalize"]&lt;/span&gt;

&lt;span class="nn"&gt;[update.normalize]&lt;/span&gt;
&lt;span class="py"&gt;paths&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;["Shared/App/Resources"]&lt;/span&gt;
&lt;span class="py"&gt;sourceLocale&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"en"&lt;/span&gt;
&lt;span class="py"&gt;harmonizeWithSource&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;sortByKeys&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="nn"&gt;[lint]&lt;/span&gt;
&lt;span class="py"&gt;paths&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;["Shared/App/Resources"]&lt;/span&gt;
&lt;span class="py"&gt;duplicateKeys&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;emptyValues&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that &lt;code&gt;Shared/App/Resources&lt;/code&gt; is the path my &lt;code&gt;.lproj&lt;/code&gt; folders are within, including the &lt;code&gt;Localizable.strings&lt;/code&gt; files.&lt;/p&gt;

&lt;p&gt;Then I configured this build script above the "Compile Sources" step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;which bartycrouch &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;bartycrouch update &lt;span class="nt"&gt;-x&lt;/span&gt;
    bartycrouch lint &lt;span class="nt"&gt;-x&lt;/span&gt;
&lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"warning: BartyCrouch not installed, download it from https://github.com/Flinesoft/BartyCrouch"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;update&lt;/code&gt; subcommand with the &lt;code&gt;normalize&lt;/code&gt; option &lt;code&gt;harmonizeWithSource&lt;/code&gt; set to &lt;code&gt;true&lt;/code&gt; ensures that all keys I add to the &lt;code&gt;sourceLocale&lt;/code&gt; "en" will automatically be added to any other languages my app supports. Additionally, I prefer to sort all keys alphabetically (&lt;code&gt;sortByKeys&lt;/code&gt;) to keep any keys with the same prefix like &lt;code&gt;Onboarding.LoginScreen.&lt;/code&gt; grouped together.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;lint&lt;/code&gt; subcommand with &lt;code&gt;emptyValues&lt;/code&gt; set to &lt;code&gt;true&lt;/code&gt; ensures that I get warnings within Xcode for any empty localization values. The &lt;code&gt;duplicateKeys&lt;/code&gt; option ensures that I also get warned if I accidentally would add a key to the "en" localization manually to prevent ambiguity.&lt;/p&gt;

&lt;p&gt;So BartyCrouch seems to continue helping with localization, even with SwiftUI. But it requires a manual step to add a key to the source language at the moment and it can't check for the existence of the key in the localization files.&lt;/p&gt;

&lt;p&gt;To explore how I can fix this, I wrote a shadow type named &lt;code&gt;SafeLocalizedStringKey&lt;/code&gt; that conforms to the same protocols  and passes the localized Strings along to &lt;code&gt;LocalizedStringKey&lt;/code&gt;. Additionally, I overloaded all SwiftUI view initializers that were using &lt;code&gt;LocalizedStringKey&lt;/code&gt; with a &lt;code&gt;safe:&lt;/code&gt; variant that has a validation for the existence of keys that would only run during development, but would safely fallback to the key in production. I achieved that by using this validation method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;validateAvailabilityInSupportedLocales&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;stringsKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cp"&gt;#if DEBUG&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;missingLocales&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Bundle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;localizations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;locale&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;localeBundle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bundle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forResource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ofType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"lproj"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;localizedValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;NSLocalizedString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stringsKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;localeBundle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;localizedValue&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;stringsKey&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;localizedValue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isEmpty&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="n"&gt;missingLocales&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isEmpty&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;assertionFailure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Missing locales &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;missingLocales&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt; for localized string key '&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;stringsKey&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;'."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="cp"&gt;#endif&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code first filters the locales that don't have an entry for the key (in which case the value is equal to the key) and reports them as an &lt;code&gt;assertionFailure&lt;/code&gt;, which ensures that this code doesn't crash the app in production even if the &lt;code&gt;DEBUG&lt;/code&gt; was set for production builds accidentally.&lt;/p&gt;

&lt;p&gt;You can find the full implementation of the &lt;code&gt;SafeLocalizedStringKey&lt;/code&gt; type in &lt;a href="https://gist.github.com/Jeehut/c8c9a8caf8dc7c02583a4a07dfbb37aa"&gt;this GitHub gist&lt;/a&gt;, including the &lt;code&gt;safe:&lt;/code&gt; overload extensions for 17 different SwiftUI views. If you copy that file into your SwiftUI project, you should be able to replace any calls like &lt;code&gt;Text("E-Mail")&lt;/code&gt; with the safe alternative &lt;code&gt;Text(safe: "E-Mail")&lt;/code&gt;. This will run the additional validation and prevent you from forgetting to add the key to your localization files as it will crash the app during development with a clear warning.&lt;/p&gt;

&lt;p&gt;So far so good, I'm happy enough with this solution for now. Improvements were made on all of the localization flaws.&lt;/p&gt;

&lt;p&gt;Of course it would be even better to have a solution that automatically takes care of adding the keys to the localization files like the "Pro localization workflow" in my previous article, but that's out of scope of this exploration as I want to focus on getting my app closer to release first. &lt;/p&gt;

&lt;p&gt;The only other improvement I actually did was to write a custom lint check using &lt;a href="https://github.com/Flinesoft/AnyLint"&gt;AnyLint&lt;/a&gt; to make sure I never forget to use the &lt;code&gt;safe:&lt;/code&gt; APIs over the default ones. The check even autocorrects the non-safe API usage to safe APIs automatically and could be run as a pre-commit hook. I've uploaded it as a &lt;a href="https://gist.github.com/Jeehut/c3721a53aca3b7fb8c2e14f4bda32935"&gt;GitHub gist&lt;/a&gt; in case you want to use it.&lt;/p&gt;

&lt;p&gt;That's it from me today, I hope you found something useful in this article. What do you think of my approach taken here? Do you have other ideas that can improve localization with SwiftUI? Let me know by leaving a comment below.&lt;/p&gt;

&lt;p&gt;Thank you for your time!&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>localization</category>
      <category>swiftui</category>
    </item>
  </channel>
</rss>
