<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Baby CTO: Code Craft]]></title><description><![CDATA[Bite-sized tutorials and snippets that demystify specific, in-depth techniques. Elevate your coding game, one craft at a time.]]></description><link>https://www.baby-cto.com/s/code-craft</link><image><url>https://substackcdn.com/image/fetch/$s_!u3-p!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4c9d8fc-b387-47a4-b77f-c2aa0a303dc7_619x619.png</url><title>Baby CTO: Code Craft</title><link>https://www.baby-cto.com/s/code-craft</link></image><generator>Substack</generator><lastBuildDate>Mon, 11 May 2026 06:53:28 GMT</lastBuildDate><atom:link href="https://www.baby-cto.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Rémy]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[babycto@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[babycto@substack.com]]></itunes:email><itunes:name><![CDATA[Rémy]]></itunes:name></itunes:owner><itunes:author><![CDATA[Rémy]]></itunes:author><googleplay:owner><![CDATA[babycto@substack.com]]></googleplay:owner><googleplay:email><![CDATA[babycto@substack.com]]></googleplay:email><googleplay:author><![CDATA[Rémy]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Confront your greatest fear and parse a string with a Regular Expression]]></title><description><![CDATA[In this blog we usually talk about tech management but let's have a refreshing tutorial on a less advanced topic!]]></description><link>https://www.baby-cto.com/p/confront-your-greatest-fear-and-parse</link><guid isPermaLink="false">https://www.baby-cto.com/p/confront-your-greatest-fear-and-parse</guid><dc:creator><![CDATA[Rémy]]></dc:creator><pubDate>Mon, 01 Apr 2024 19:04:38 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/76d938da-e203-43d6-b7a4-02b4938b9964_1312x928.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Regular expressions are a scary thing and can take quite a while to be digested &#8212; even for mid-level developers. Many useful tools such as <a href="https://regex101.com/">regex101</a> that will decode the syntax for you or <a href="https://www.debuggex.com/">debuggex</a> which will let you visualize the expression as finite state machine.</p><p>But nothing like putting your hands in the dirt to understand how something really works! Something that eluded me for years is how you could parse a string &#8212; and in particular if there is an escaped quote in it?</p><p>Let&#8217;s start with the beginning. We want to match a basic string. Regular expressions are expressed in JS.</p><pre><code># To match
"hello"

# Regxp
/".*"/</code></pre><p>The structure is simple: first a quote, then any character any number of time, then another quote. But in real life you&#8217;re probably working on a parser. For example:</p><pre><code>&lt;something foo="bar" bar="foo" /&gt;</code></pre><p>In this case the regular expression is going to get greedy and return <code>"bar" bar="foo"</code>, which is not what we want.</p><p>The first trick is probably to tell the regular expression not to be greedy by using the <code>?</code> symbol.</p><pre><code># To match
&lt;something foo=<strong>"bar"</strong> bar=<strong>"foo"</strong> /&gt;

# Regular expression
/".*<strong>?</strong>"/</code></pre><p>That&#8217;s fine but now if like in most cases you want to allow your users to have quotes in the string by escaping them, you&#8217;ll be out of luck. This for example will not work:</p><pre><code>const name = "Dwayne \"The Rock\" Johnson";
// Will match: "Dwayne \" and " Johnson"</code></pre><p>This part got me perplexed for the longest time. There are different ways to solve it, my personal favorite is to consider what we want to allow within our string. Namely:</p><ul><li><p>Any character that isn&#8217;t an end quote is fine: <code>[^"]</code> in regex language (<code>^</code> is for <em>not</em>)</p></li><li><p>Any escape sequence &#8212; aka something that starts with a backslash: <code>\\.</code> in regex</p></li></ul><p>Since we don&#8217;t want the first match to eat up the second match (afterall a &#8220;backslash&#8221; is &#8220;not a quote&#8221;), we&#8217;ll make sure to put them in the right order so that the matching can happen easily.</p><pre><code># To match
const name = <strong>"Dwayne \"The Rock\" Johnson"</strong>;

# Regular Expression
/"(<strong>\\.</strong>|<strong>[^"]</strong>)*"/</code></pre><p>And that&#8217;s it! You are now matching an escaped string. Not that scary anymore?</p><p>Let&#8217;s study the second method, that I&#8217;ve found <a href="https://github.com/lark-parser/lark/blob/d676df9b888ead42daffd31c035d95241bff0920/lark/grammars/common.lark#L23">inside of Lark</a> (amazing package by the way). It&#8217;s both simpler and more confusing and does not work with older JavaScript engines, but let&#8217;s go into it.</p><p>Essentially, if you say that &#8220;escaped quotes must not terminate the string&#8221; then it means that &#8220;the last quote of the string can&#8217;t be escaped&#8221;. That&#8217;s something we can easily check with a negative assertion:</p><pre><code># To match
const name = <strong>"Dwayne \"The Rock\" Johnson"</strong>;

# Regular Expression
/".*?<strong>(?&lt;!\\)</strong>"/</code></pre><p>The novelty here is that instead of just having a non-greedy match-all ( <code>.*?</code> ), we&#8217;re adding at the end an assertion <code>(?&lt;!\\)</code> to check that there is no backslash before the end. This has however a drawback, it&#8217;s that you can&#8217;t escape a backslash right before the end of the string, because then the last quote would be preceded by a backslash (still with me?). In short, this doesn&#8217;t work:</p><pre><code>const effect = "Domino \\";</code></pre><p>But fortunately, we can allow to terminate the string with quoted backslashes!</p><pre><code># To match
const effect = <strong>"Domino \\"</strong>;

# Regular Expression
".*?(?&lt;!\\)<strong>(\\\\)*?</strong>"</code></pre><p>And here we are! Matching strings another way.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.baby-cto.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.baby-cto.com/subscribe?"><span>Subscribe now</span></a></p><p>Let&#8217;s hope that this problem-oriented walkthrough helped you understand relatively advanced thought patterns in regular expression. Often you&#8217;ll walk on problems that can seem intractable without the proper knowledge but which can easily be unlocked if you master regular expressions &#8212; or better even: parsers! But that&#8217;s for another article.</p><p></p><p></p><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[Hands on! Parse your emails with Google's Gemma]]></title><description><![CDATA[Brave the impossible and use only your CPU to transform your emails into machine-readable semantic JSON files which can then be interpreted by personal assistants, finance tools, etc.]]></description><link>https://www.baby-cto.com/p/hands-on-parse-your-emails-with-googles</link><guid isPermaLink="false">https://www.baby-cto.com/p/hands-on-parse-your-emails-with-googles</guid><dc:creator><![CDATA[Rémy]]></dc:creator><pubDate>Sun, 25 Feb 2024 08:00:59 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/990b4c02-36f5-4322-8f4a-41ce4f0d789e_1312x928.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As I explained in my <a href="https://babycto.substack.com/p/jobs-for-llms-and-how-to-survive">previous post</a>, LLMs are not good at everything but they&#8217;re particularly good at parsing information and transforming it into another format. It&#8217;s a technique that we use everywhere <a href="https://www.chatfaq.io/blog/ai-snack-guided-llm-generation-shaping-language-models-to-meet-your-needs">in ChatFAQ</a> for example.</p><p>Today we&#8217;re going to dive into the code that lets us do this. The goal is simple:</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.baby-cto.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Baby CTO! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><ol><li><p>First we&#8217;ll classify the email to know what kind of email it is. Yeah I said that it&#8217;s not a great idea because LLMs are not super performant for that. I&#8217;ve tried my best, it seems to work well with GPT-4 and more or less decently with Gemma.</p></li><li><p>And then for each type of email we&#8217;re going to extract a JSON which tells us in a machine-readable format the content of that email.</p></li></ol><p>I&#8217;ll walk through the main elements of the code, if you want to follow up with the completed project it&#8217;s <a href="https://github.com/Xowap/semmail">all on GitHub</a>.</p><p>Also, of course there are many libraries and frameworks and whatnots to help you do this in different ways, but we&#8217;re here to learn so we do all by hand today.</p><p>Buckle up and let&#8217;s go!</p><h2>The flask app</h2><p>In order to do this, we&#8217;re going to make a Flask app which exposes:</p><ul><li><p>A basic page that allows to upload an email in the .eml format (what you get when you &#8220;Download this email&#8221; from Gmail for example).</p></li><li><p>An API which for a given email gives you the semantics of it.</p></li></ul><p>We&#8217;ll do that in a very small <a href="https://github.com/Xowap/semmail/blob/e305aee3a3e523463de66390b8ece765b6a08ae8/src/semmail/app.py">src/semmail/app.py</a> file.</p><p>First, a super basic view which is just a form that will call the API when submitted.</p><pre><code>@app.route("/")
def home():
    """Just a dumb form where you can upload a file to the API"""

    return render_template_string(
        """
        &lt;!DOCTYPE html&gt;
        &lt;html&gt;
        &lt;head&gt;
            &lt;title&gt;Upload File&lt;/title&gt;
        &lt;/head&gt;
        &lt;body&gt;
        &lt;h2&gt;Upload File&lt;/h2&gt;
        &lt;form action="/upload" method="post" enctype="multipart/form-data"&gt;
            &lt;input type="file" name="email_file"&gt;
            &lt;input type="submit" value="Upload"&gt;
        &lt;/form&gt;
        &lt;/body&gt;
        &lt;/html&gt;
    """
    )</code></pre><p>Then the API itself. We&#8217;re going to expect that there is a function that takes an email as input and returns the parsed output.</p><pre><code>@app.route("/upload", methods=["POST"])
def upload_file():
    """After a very basic validation of the file, we put it through the LLM
    so that we can know what it's about."""

    if "email_file" not in request.files:
        return jsonify({"error": "No file part"}), 400

    file = request.files["email_file"]

    if file.filename == "":
        return jsonify({"error": "No selected file"}), 400
    try:
        file_content = file.read()
        result = interpret_email(file_content)
        return jsonify(result), 200
    except ValueError as e:
        return jsonify({"error": str(e)}), 400</code></pre><p>In the middle of the boilerplate, you&#8217;ll figure the <code>interpret_email()</code> function. For now it can just return a static structure, we&#8217;re going to implement it in a moment.</p><h2>Calling the AI</h2><p>Because in the project I want to have an OpenAI and a Gemma implementation I&#8217;ll have <a href="https://github.com/Xowap/semmail/tree/e305aee3a3e523463de66390b8ece765b6a08ae8/src/semmail/ai">two modules</a> in the GitHub but I&#8217;ll only cover Gemma because it&#8217;s the hot new kid that everyone wants to play with.</p><h3>Make the instance</h3><p>The first thing you need to do is to go to the model&#8217;s <a href="https://huggingface.co/google/gemma-2b-it">HuggingFace page</a> and use your account to accept the license. Then go to your settings and fetch an API key that you&#8217;ll need to put in the project&#8217;s <code>.env</code>.</p><pre><code>HUGGINGFACE_TOKEN=xxx</code></pre><p>This will allow us to create the instance of the tokenizer and the model in our <a href="https://github.com/Xowap/semmail/blob/e305aee3a3e523463de66390b8ece765b6a08ae8/src/semmail/ai/gemma.py">src/semmail/ai/gemma.py</a> file. Put at the root of the module:</p><pre><code>model_id = "google/gemma-2b-it"

if torch.cuda.is_available():
    device = "cuda"
else:
    device = "cpu"


tokenizer = lazy_object_proxy.Proxy(lambda: AutoTokenizer.from_pretrained(model_id))
model = lazy_object_proxy.Proxy(
    lambda: AutoModelForCausalLM.from_pretrained(model_id).to(device)
)
login_done = False


def ensure_login():
    """Makes sure that we're logged into HuggingFace Hub so that we can
    download the LLM (which requires to approve a license)."""

    global login_done

    if not login_done:
        login(token=environ["HUGGINGFACE_TOKEN"])
        login_done = True</code></pre><p>A bunch of things to unpack:</p><ul><li><p>Everything is wrapped in a <a href="https://pypi.org/project/lazy-object-proxy/">lazy_object_proxy</a>, which avoids to blow up the CPU and RAM at moment the module is imported. It will wait that the function is called a first time for that. You&#8217;ll thank me later.</p></li><li><p>We create an ensure_login() function which allows subsequent functions to make sure that we&#8217;re logged into huggingface_hub, but also does it only once to avoid having to do this every time we call the AI.</p></li><li><p>There is a conditional detection of CUDA to enable it or not depending on the availability. You guys tell me if it works, I&#8217;m an idiot who didn&#8217;t check his GPU&#8217;s compatibility before buying it.</p></li></ul><h3>Communicate with Gemma</h3><p>You&#8217;ve probably noted the name of the model, <code>google/gemma-2b-it</code>.</p><ul><li><p>The &#8220;2b&#8221; indicates the size of the model. I&#8217;m using this one and not the bigger one because it uses a lot less resources and can realistically be used on a CPU while the other one cannot.</p></li><li><p>The "it&#8221; tells you that it has been trained for chat-like interactions.</p></li></ul><p>So how do you get anything about this chat training? It means that your prompt has to follow this structure:</p><pre><code>&lt;start_of_turn&gt;user
How does the brain work?&lt;end_of_turn&gt;
&lt;start_of_turn&gt;model</code></pre><p>It indicates to the model the alternance between human and model speakers. Sadly, it does not have a system prompt to also guide the LLM outside of this, but we&#8217;ll go around that.</p><p>The idea is to use it the following way:</p><pre><code>def ask_gemma(instruction: str, this_input: str, max_tokens: int = 1000) -&gt; str:
    ensure_login()

    chat = [
        {
            "role": "user",
            "content": f"# Instructions\n{instruction}\n# Input\n{this_input}",
        },
    ]

    prompt = tokenizer.apply_chat_template(
        chat,
        tokenize=False,
        add_generation_prompt=True,
    )
    inputs = tokenizer.encode(
        prompt,
        add_special_tokens=True,
        return_tensors="pt",
    )
    outputs = model.generate(
        input_ids=inputs.to(model.device),
        max_new_tokens=max_tokens,
    )

    convo_raw = tokenizer.decode(outputs[0])
    convo: Sequence[Dict] = parser.parse(convo_raw)  # noqa

    return convo[-1]["content"]</code></pre><p>What you see here is that we&#8217;re using the tools from the transformers lib to generate the chat template prompt and give it to the LLM. Then when it runs, you receive a response that contains both the question and the answer from the bot, and&#8230; It becomes a bit confusing.</p><p>To be honest I&#8217;m not entirely sure how I&#8217;m supposed to parse this or if there are utilities in the transformers lib (I didn&#8217;t find them), so I&#8217;ve written my own parser (which you see used in the code above).</p><p>If you&#8217;ve outgrown your fear of regular expressions you may outgrow your fear of parsers as well. It&#8217;s relatively easy to write using the <a href="https://lark-parser.readthedocs.io/en/stable/">Lark package</a>. It&#8217;s not the scope of this article, just <a href="https://github.com/Xowap/semmail/blob/e305aee3a3e523463de66390b8ece765b6a08ae8/src/semmail/ai/gemma.py#L67">check the grammar</a> if you&#8217;re interested. What matters is that we&#8217;ve got a parser!</p><p>Another thing is that you&#8217;ve noticed how there are two important parameters:</p><ul><li><p>instruction &#8212; Corresponds to the system prompt, tells the bot what to do</p></li><li><p>this_input &#8212; The user input</p></li></ul><p>Since there is no management of system prompt in this fine-tuning, I&#8217;m just bulding a prompt from those two and hoping that the LLM picks it up (it does).</p><p>At this point we have a function that runs the LLM locally on your CPU/GPU. Pretty neat!</p><h2>Getting the email&#8217;s text</h2><p>Emails might be the oldest and most inconsistent standard in the Internet world. Their encoding is super confusing and while Python has a built-in library that implements all the heavy lifting, it really comes as a kit that you&#8217;ve got to assemble yourself (without the instructions).</p><p>The strategy is as follows:</p><ol><li><p>Go through the different &#8220;parts&#8221; of the email, looking either for a plain text or a HTML attachment. With a preference for the HTML attachment, because sometimes you will find a plain text attachment that turns out to be bullshit, so sadly only the HTML is reliable.</p></li><li><p>Because the HTML is pretty fat, if that&#8217;s what we&#8217;re going for we&#8217;ll make sure to convert it into Markdown. This will greatly reduce the amount of tokens and absolutely reduce the complexity of understanding the message for the LLM.</p></li></ol><p>This is all the job of the <a href="https://github.com/Xowap/semmail/blob/e305aee3a3e523463de66390b8ece765b6a08ae8/src/semmail/parser.py#L80">parse_email()</a> function, which I&#8217;m not going to detail because it would be off-topic. What you need to know is that it outputs the email in a simplified text format which looks like:</p><pre><code>From: foo@bar.com
To: someone@example.com
Date: 2024-02-23 20:12:00 +0100
Subject: Some email

Blah blah blah this is the content of the email</code></pre><p>It&#8217;s something we can easily give to our LLM.</p><h2>Plain text to JSON</h2><p>Now the useful part. The core of this project is to convert plain text into JSON, isn&#8217;t it? <a href="https://github.com/Xowap/semmail/blob/e305aee3a3e523463de66390b8ece765b6a08ae8/src/semmail/ai/__init__.py#L22">Let&#8217;s do that</a>!</p><pre><code>def parse_to_json(
    prompt: str, text: str, schema: Any, attempts: int = 3
) -&gt; Optional[Any]:
    for _ in range(attempts):
        parsed_raw = ask(prompt, text)
        parsed_raw = MD_START.sub("", parsed_raw)
        parsed_raw = MD_END.sub("", parsed_raw)
        try:
            parsed = yaml.safe_load(parsed_raw)
            jsonschema.validate(parsed, schema)
        except (yaml.YAMLError, jsonschema.ValidationError):
            pass
        else:
            return parsed</code></pre><p>What do we see here:</p><ul><li><p>First we clean up the model for any Markdown enclosing. <em>Sometimes</em> chat-tuned models like to put YAML within <code>```yaml</code> quotes. We make sure to remove it if this happens.</p></li><li><p>Then we try to load the YAML data. Why YAML and not JSON? Easy: JSON is a sub-set of YAML so if the model decides to output JSON it will still work, but on the other hand YAML is more permissive and uses less tokens than JSON. So it&#8217;s both safer and more economic to use.</p></li><li><p>If all went well, we validate the parsed structure against the provided JSON schema. This ensures that the output corresponds to the constraints that we need to work with.</p></li><li><p>And if not or if the validation fails, we try again. Most LLMs will not strictly always have the same output for a given input so it doesn&#8217;t hurt to try another time to see if it&#8217;s still broken.</p></li></ul><h3>Prompting</h3><p>In order to parse the different elements, we&#8217;ll use a <a href="https://github.com/Xowap/semmail/blob/e305aee3a3e523463de66390b8ece765b6a08ae8/src/semmail/prompts.py">prompt library</a>. For each prompt, we associate a JSON schema which helps validating the output.</p><p>I&#8217;m not going to go through every single prompt because if you&#8217;re human you can probably understand them but I&#8217;m just going to explain <a href="https://github.com/Xowap/semmail/blob/e305aee3a3e523463de66390b8ece765b6a08ae8/src/semmail/prompts.py#L12">the one I use to classify emails</a> because it&#8217;s the hard one.</p><p>My goal here is to determine the probability of each email type by asking explicitly the LLM to give that probability according to different factors that I give him. The idea is that you can then easily pick the email type by checking which is the category with the highest probability. We rely on the LLM&#8217;s feelings but we&#8217;re using hard Python algorithms to take the decision.</p><p>The prompt goes as follows:</p><pre><code>Take a deep breath.

You will analyze an email. For this email you need to determine the
likeliness that this email belongs to a specific category. This works
with a score system. For each category you MUST give a score of 0 if
you are sure that it's not from that category, a score of 1 if you are
sure or a number between 0 or 1 that reflects how much you want to
give that category to the email.

The categories are:
    - Commercial is a prospective email.
    - Bill is an invoice or a bill for a sold service or product.
    - Conversation is a regular conversation between humans.

Here are elements to look for in an email. For each element, if I tell 
you +X then consider that it's adding points to that aspect and -X is 
removing points.

Has few sentences +bill -commercial
Has a total price +bill
Has a list of items sold +bill
Has "bill", "invoice", "order confirmation" or any synonym in the Subject +bill -commercial -conversation
Presents several product benefits +commercial -bill
Is structured in a Hello/Message/Signature way +conversation -bill
Different signatures and quoted mails +conversation -bill -commercial

Now return the following YAML:

bill: x
commercial: x
conversation: x

You need to replace "x" by the score. If you want to give a score of
1 to two or more categories, you need to think harder to make the
difference.

Make sure that the output is pure YAML, not wrapped in Markdown, no sentences.</code></pre><p>You can see the structure:</p><ol><li><p>The LLM receives a general purpose</p></li><li><p>Then I explain the categories</p></li><li><p>Then I give the different factors for the different categories so that the LLM knows what to look for (it&#8217;s really not working if you don&#8217;t do this)</p></li><li><p>Then I give the YAML schema at the end. If the schema is too high in the prompt the LLM tends to forget about it</p></li><li><p>And then some banalities about the output to avoid getting stuff like &#8220;Of course, here is your YAML&#8221;, which would screw the parsing</p></li></ol><p>This is matched up by a JSON schema:</p><pre><code>{
    "type": "object",
    "properties": {
        "commercial": {"type": "number", "minimum": 0, "maximum": 1},
        "bill": {"type": "number", "minimum": 0, "maximum": 1},
        "conversation": {"type": "number", "minimum": 0, "maximum": 1},
    },
    "required": ["commercial", "bill", "conversation"],
}</code></pre><p>With this strategy, I&#8217;ve written 4 prompts:</p><ul><li><p>The one you&#8217;ve just seen to determine the email&#8217;s type</p></li><li><p>If it&#8217;s a commercial email, extract the name of the product and the USP</p></li><li><p>If it&#8217;s a bill, extract the total price and the purchased items</p></li><li><p>If it&#8217;s a conversation, make a summary of the conversation</p></li></ul><h2>Deciding</h2><p>And all this culminates into the <a href="https://github.com/Xowap/semmail/blob/e305aee3a3e523463de66390b8ece765b6a08ae8/src/semmail/parser.py#L111">interpret_email()</a> function.</p><pre><code>def interpret_email(email: bytes) -&gt; Any:
    """Uses a first round of LLM in order to determine the type of message,
    then proceeds to using a specific prompt for that type in order to parse
    the email into a JSON output."""

    parsed_email = parse_email(email)

    email_type_proba = parse_to_json(
        DETERMINE_TYPE.prompt,
        parsed_email,
        DETERMINE_TYPE.schema,
    )

    email_type = max(email_type_proba.items(), key=lambda p: p[1])[0]
    extra = {}

    if email_type == "commercial":
        extra["commercial"] = parse_to_json(
            COMMERCIAL_INFO.prompt,
            parsed_email,
            COMMERCIAL_INFO.schema,
        )
    elif email_type == "bill":
        extra["bill"] = parse_to_json(
            BILL_INFO.prompt,
            parsed_email,
            BILL_INFO.schema,
        )
    elif email_type == "conversation":
        extra["conversation"] = parse_to_json(
            CONVERSATION_INFO.prompt,
            parsed_email,
            CONVERSATION_INFO.schema,
        )

    return dict(
        email_type=dict(
            chosen=email_type,
            proba=email_type_proba,
        ),
        **extra,
    )</code></pre><p>Which is very simple:</p><ul><li><p>First we determine the email type, which we get as a JSON object</p></li><li><p>And then we use one of the three parsers to get the extra information relative to this type</p></li><li><p>And finally we output a JSON with the extracted information and the decision-making values that we&#8217;ve used</p></li></ul><p>So if I take my latest Amazon purchase, I&#8217;m getting the following output:</p><pre><code>{
  "bill": {
    "bought": [
      {
        "label": "L'investisseur eclaire: Cultiver son...",
        "price": [
          37.46,
          "EUR"
        ]
      }
    ],
    "total": [
      43.72,
      "EUR"
    ]
  },
  "email_type": {
    "chosen": "bill",
    "proba": {
      "bill": 1,
      "commercial": 0.2,
      "conversation": 0
    }
  }
}</code></pre><p>Hooray! It worked!</p><h2>Conclusion</h2><p>I&#8217;ve showcased two things in this article:</p><ul><li><p>Using extremely small boilerplate code and pretty conventional tools, I can easily leverage LLMs to parse generic content into usable JSON. It&#8217;s something that was completely unthinkable a few months ago!</p></li><li><p>And I can do so using a local LLM that even runs on &#8220;commodity&#8221; hardware (be sure to have 10 Gio of RAM before starting the proejct, or you&#8217;ll see how fast your computer can freeze)</p></li></ul><p>This is an exciting time because long-standing problems are finally getting solved. In a near future you can expect to see every single tool out there getting <em>a lot</em> smarter when it comes to understanding human text.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.baby-cto.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Baby CTO! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Revisited: 10 rules to code like NASA (applied to interpreted languages)]]></title><description><![CDATA[Discover NASA's secret to robust software. Dive into adapted Power of 10 guidelines for modern languages. Master stable, clear code with NASA-level insights!]]></description><link>https://www.baby-cto.com/p/10-rules-to-code-like-nasa</link><guid isPermaLink="false">https://www.baby-cto.com/p/10-rules-to-code-like-nasa</guid><dc:creator><![CDATA[Rémy]]></dc:creator><pubDate>Thu, 17 Aug 2023 13:28:38 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/d75d08b0-24d1-4f7b-84e0-a11f4695bf3d_1312x928.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p><strong>Foreword</strong> &#8212; Dear beginner, dear not-so-beginner, dear reader. This article is a lot to take in. You'll need perspective for it to make sense. Once in a while, take a step back and re-think about all the concepts explained here. They helped me a lot over the years, and I hope that they will help you too. This article is my interpretation of them for the work I do, which is mostly web-related development.</p></blockquote><p>NASA's <a href="https://en.wikipedia.org/wiki/JPL">JPL</a>, which is responsible for some of the most awesomest science out there, is quite famous for its <a href="https://en.wikipedia.org/wiki/The_Power_of_10:_Rules_for_Developing_Safety-Critical_Code">Power of 10</a> rules (<a href="http://spinroot.com/gerard/pdf/P10.pdf">see original paper</a>). Indeed, if you are going to send a robot on Mars with a 40 minutes ping and no physical access to it then you pretty damn well should make sure that your code doesn't have bugs.</p><p>These rules were made with embedded software in mind but why wouldn't everybody be able to benefit from this? Could we apply them to other languages like JavaScript and Python &#8212; and thus make web applications more stable?</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.baby-cto.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Baby CTO is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>That's a question I have been considering for years and here is my interpretation of the 10 rules applied to interpreted languages and web development, revisited some time after the <a href="https://dev.to/xowap/10-rules-to-code-like-nasa-applied-to-interpreted-languages-40dd">initial post</a>, with comments in mind.</p><h2>1 &#8212; Avoid complex flow constructs</h2><blockquote><p><strong>Original rule</strong> &#8212; Restrict all code to very simple control flow constructs &#8211; do not use <code>goto</code> statements, <code>setjmp</code> or <code>longjmp</code> constructs, and direct or indirect recursion.</p></blockquote><p>When you use weird constructs then your code becomes difficult to analyze and to predict. The generations that came out after <code>goto</code><a href="https://homepages.cwi.nl/~storm/teaching/reader/Dijkstra68.pdf"> was considered harmful</a> did indeed avoid using it. We're at the stage where we're debating if <code>continue</code><a href="https://github.com/airbnb/javascript/issues/1103"> is </a><code>goto</code> and thus should be banned.</p><p>My take on this is that <code>continue</code> in a loop is exactly the same as <code>return</code> in a <code>forEach()</code> (especially now that JS has block scoping) so if you're saying that <code>continue</code> is <code>goto</code> then you're basically closing your eyes on the issue. But that's a JS-specific implementation detail.</p><p>As a general rule you should avoid everything that is mind-bending or hard to spot because if your brain power is spent understanding the quirks of jumping around then you're not spending it on the actual logic and then you might be <a href="https://www.youtube.com/watch?v=vJG698U2Mvo">hiding some bugs</a> without your knowledge.</p><p>I'll let you be the judge of what you put in that category but I would definitely put:</p><ul><li><p><code>goto</code> itself of course</p></li><li><p>PHP's <code>continue</code> and <code>break</code> used in conjunction with numbers, which is just pure insanity</p></li><li><p><code>switch</code> constructs, because they usually require a <code>break</code> to close the block and I guarantee you that there <em>will be</em> bugs. A series of <code>if</code>/<code>else if</code> will do the same job in a non-confusing manner, as well as <code>match</code>-like constructs in languages like Python or Crablang.</p></li></ul><p>Besides this, avoid of course recursions, for several reasons:</p><ul><li><p>As they build on the call stack, whose size is very limited, you can't really control how deep your recursion can go. Even if your code is legit, it might fail because it recurses too much.</p></li><li><p>It&#8217;s easier to put safeguards when working in non-recursive mode &#8212; think explored paths or node IDs.</p></li><li><p>Do you get this feeling when doing recursions where you don't really know if your code is ever going to stop? It's very hard to imagine a recursion and to prove that it will stop correctly at the end.</p></li><li><p>It's also more compatible with the following rules to use an iterative algorithm instead of a recursive one, because you have more control (again) on the size of the problem you're dealing with.</p></li></ul><p>As a bonus, recursions can often come as an intuitive implementation of an algorithm but is usually also far from optimal. By example we often ask in job interviews to implement the factorial function using a recursive function but that's far less efficient than an iterative implementation. Regular expressions too <a href="https://dev.to/xowap/how-cloudflare-could-have-avoided-its-outage-maybe-1jko">can be disastrous</a>.</p><h2>2 &#8212; All loops must have fixed bounds. This prevents runaway code.</h2><blockquote><p><strong>Original rule</strong> &#8212; All loops must have a fixed upper-bound. It must be trivially possible for a checking tool to prove statically that a preset upper-bound on the number of iterations of a loop cannot be exceeded. If the loop-bound cannot be proven statically, the rule is considered violated.</p></blockquote><p>The idea with this rule is the same as with the interdiction of recursions: you want to prevent runaway code. The way you implement this is by making sure it's trivial to prove statically that the loop won't exceed a given number of iterations.</p><p>Let's give an example in Python. You could do this:</p><pre><code>def iter_max(it, max_iter):
    cnt = 0

    for x in it:
        assert cnt &lt; max_iter
        yield x
        cnt += 1


def main():
    for i in iter_max(range(100), 10):
        print(i)</code></pre><p>A language like Python will however limit the number of iterations by itself in many cases. So if you prove that the input lists won't be too long there is a bunch of cases where you don't need to do this.</p><p>A good application of that is pagination: make sure that you always work with pages that are of a reasonable size and this way you won't need loops that could run forever. Always think your code so it only works on a finite amount of data and let tools that were made for that handle infinity (like your DB engine).</p><h2>3 &#8212; Avoid heap memory allocation</h2><blockquote><p><strong>Original rule</strong> &#8212; Do not use dynamic memory allocation after initialization.</p></blockquote><p>That makes of course no sense in interpreted languages where literally everything is allocated dynamically. But this doesn't mean that the rule does not apply to them. The core idea of the rule is that, beyond the tedious memory management techniques that you have to use in C, it's also very important to be able to fixate an upper bound in the memory consumption of your program.</p><p>So for interpreted languages it means that when you write your code, you should be able to know that given any accepted input the memory consumption won't go beyond a certain point.</p><p>While this can be hard to prove in an absolute manner, there is good clues and principles that you can follow. To be more specific and to repeat the previous sections, pagination is an essential technique. If you only work with pages and that you know that the content of each page is limited (DB fields have limited length and so on) then it's quite easy to prove that at least the data coming from those pages can be contained within an upper bound.</p><p>This is a powerful idea: load a full page of data into memory, work on it then let garbage collection discard it. It can even &#8212; under specific conditions &#8212; be a way to parallelize the work. Indeed, if you&#8217;ve managed to make your problem workable in pages, it means they can be processed independently.</p><h2>4 &#8212; Restrict functions to a single printed page</h2><blockquote><p><strong>Original rule</strong> &#8212; No function should be longer than what can be printed on a single sheet of paper in a standard reference format with one line per statement and one line per declaration. Typically, this means no more than about 60 lines of code per function.</p></blockquote><p>This is about two different things.</p><p>First, the human brain can only fully understand so much logic and the symbolic page looks about right. While this estimation is totally arbitrary you'll find that you can easily organize your code into functions of about that size or smaller and that you can easily understand those functions. Nobody likes to land on a 1000-lines function that seems to do a gazillion things at the same time. We've all been there and we know it should not happen.</p><p>Second, when the function is small &#8212; or rather as small as possible &#8212; then you can worry about giving this function the least possible power. Make it work on the smallest unit of data and let it be a super simple algorithm. It will de-couple your code and make it more maintainable.</p><p>And let me emphasis on the arbitrary aspect of this rule. It works for the very reason that it is arbitrary. Someone decided that they don't want to see a function longer than a page because it's not nice to work with if it is any longer. And they've also noticed that it is doable. At first I rejected this rule but more than a decade later I must say that if you just follow either of the goals mentioned above then your code will always fit in a page of paper. So yes, it's a good rule.</p><p>The good news is that we can even push this idea further.</p><p>First of all, lines length is important. You want your code to fit in a half-screen in order to be able to read two files side-by-side without having to scroll horizontally. This puts the limit at 80-ish (86 is becoming increasingly popular).</p><p>And secondly, you probably want to keep below 5~10 your <a href="https://en.wikipedia.org/wiki/Cyclomatic_complexity">cyclomatic complexity</a> (for example a <code>max-complexity = 5</code> in <a href="https://beta.ruff.rs/docs/settings/#mccabe-max-complexity">Ruff&#8217;s settings</a>.</p><p>Although predating the publication of the P10 paper, this complexity limit wasn&#8217;t included. my guess leans towards the complexity it would impress upon the writing of the rule which in its current state only is a few lines long. Furthermore, you need specific tools to review the cyclomatic complexity while everything mentioned in this paper can be hand-checked. It however echoes greatly with rule 1, 4 and 9 so my advice is definitely to land it into your coding guidelines.</p><h2>5 &#8212; Use a minimum of two runtime assertions per function</h2><blockquote><p><strong>Original rule</strong> &#8212; The assertion density of the code should average to a minimum of two assertions per function. Assertions are used to check for anomalous conditions that should never happen in real-life executions. Assertions must always be side-effect free and should be defined as Boolean tests. When an assertion fails, an explicit recovery action must be taken, e.g., by returning an error condition to the caller of the function that executes the failing assertion. Any assertion for which a static checking tool can prove that it can never fail or never hold violates this rule. (I.e., it is not possible to satisfy the rule by adding unhelpful "assert(true)" statements.)</p></blockquote><p>That one is tricky because you need to understand what would count as an assertion.</p><p>In the original rules, assertions are consider to be a boolean test done to verify "pre- and post- conditions of functions, parameter values, return values of functions, and loop-invariants". If the test fails then the function must do something about it, typically returning an error code.</p><p>In the context of C or Go it is mostly as simple as this. In the context of almost every other language it means raising an exception. And depending on the language, a lot of those assertions are made automatically.</p><p>To give Python as an example, you could do this:</p><pre><code>assert "foo" in bar
do_something(bar["foo"])</code></pre><p>But why bother when the fact of doing this will also raise an exception?</p><pre><code>do_something(bar["foo"])</code></pre><p>For me it's always very tempting to make as if the input value was <em>always</em> right by falling back to defaults when the input is crap. But that's usually not helpful. Instead, you should let your code fail as much as possible and use an exception reporting tool (I personally love <a href="https://sentry.io/">Sentry</a> but there is plenty out there). This way you'll know what goes wrong and you'll be able to fix your code.</p><p>Of course, this means that your code will fail at runtime. But it's all right! Runtime is not production time. If you test your application extensively before sending it to production, this will allow you to see most of the bugs. Then your real users will also encounter some bugs, but you will also be informed of them, instead of things failing silently.</p><p>As a side-note, if you don't have control over the input, like if you're doing an API by example, it's not always a good idea to fail. Raise an exception on incorrect input and you'll get an error 500 which is not really a good way to communicate bad input (since it would rather be something in the range of the 4xx status codes). In that case you need to properly validate the input before hand. However depending on who's using the code you might or might not want to report the exceptions. A few examples:</p><ul><li><p>An external tool calls your API. In that case you want to report exceptions because you want to know if the external tool is going sideways.</p></li><li><p>Another of your services calls your API. In that case you also want to report exceptions as it's yourself doing things wrong.</p></li><li><p>The general public calls your API. In that case you probably don't want to receive an email every time that someone does something wrong.</p></li></ul><p>In short it's all about knowing about the failures that you will find interesting to improve your code stability.</p><h2>6 &#8212; Restrict the scope of data to the smallest possible.</h2><blockquote><p><strong>Original rule</strong> &#8212; Data objects must be declared at the smallest possible level of scope.</p></blockquote><p>In short, don't use global variables. Keep your data hidden within the app and make it so that different parts of the code can't interfere with each other.</p><p>You can hide your data in classes, modules, second-order functions, etc.</p><p>One thing though is that when you're doing unit testing then you'll notice that this sometimes backfires to you because you want to set that data manually just for the test. This might mean that you need to hide your data away but keep a way to change it which you conventionally won't use. That's the famous <code>_name</code> in Python or <code>private</code> in other languages (which can still be accessed using reflection).</p><h2>7 &#8212; Check the return value of all non-void functions, or cast to void to indicate the return value is useless.</h2><blockquote><p><strong>Original rule</strong> &#8212; The return value of non-void functions must be checked by each calling function, and the validity of parameters must be checked inside each function.</p></blockquote><p>In C, the mostly-used way of indicating an error is by the return value of the corresponding function (or by reference into an error variable). However, with most interpreted languages it's simply not the case since errors are indicated by an exception. Even <a href="https://www.php.net/manual/en/language.errors.php7.php">PHP 7</a> improved that (even if you still get warnings printed as HTML in the middle of your JSON if you do something non-fatal).</p><p>So in truth this rule is: let errors bubble up until you can handle them (by recovering and/or logging the error). In languages that have exceptions it's pretty simple to do, simply don't catch the exceptions until you can handle them properly.</p><p>See it another way: don't catch exceptions too early and don't silently discard them. Exceptions are meant to crash your code if needs to be and the proper way to deal with exceptions is to report them and fix the bug. Especially in web development where an exception will just result in a 500 response code without dramatically crashing the whole front-end.</p><h2>8 &#8212; Use the preprocessor sparingly.</h2><blockquote><p><strong>Original rule</strong> &#8212; The use of the preprocessor must be limited to the inclusion of header files and simple macro definitions. Token pasting, variable argument lists (ellipses), and recursive macro calls are not allowed. All macros must expand into complete syntactic units. The use of conditional compilation directives is often also dubious, but cannot always be avoided. This means that there should rarely be justification for more than one or two conditional compilation directives even in large software development efforts, beyond the standard boilerplate that avoids multiple inclusion of the same header file. Each such use should be flagged by a tool-based checker and <br>justified in the code.</p></blockquote><p>In C code, the macros are a particularly efficient way to hide the mess. They allow you to <em>generate</em> C code, mostly like you would write a HTML template. It's easy to understand that it's going to be used sideways and actually you can have a look at the <a href="https://www.ioccc.org/">IOCCC</a> contestants which usually make a very heavy use of C macros to generate totally unreadable code.</p><p>However C (and C++) is mostly the only mainstream language making use of this, so how would you translate this into other languages? Did we get rid of the problem? Does compiling code into other code that will then be executed sound familiar to someone?</p><p>Yes, I'm talking about the huge pile of things we put in our Webpack configurations.</p><p>The initial rule recognizes the need for macros but asks that they are limited to "simple macro definitions". What is the "simple macro" of Webpack? What is the good transpiler and the bad transpiler?</p><p>My rationale is simple:</p><ul><li><p>Keep the stack as small as possible. The less transpilers you have the less complexity you need to handle.</p></li><li><p>Stay as mainstream as possible. By example I always use Webpack to transpile my JS/CSS, even in Python or PHP projects. Then I use a simple wrapper around a manifest file to get the right file paths on the server side. This allows me to stay compatible with the rest of the JS world without having to write more than a simple wrapper. Another way to put it is: stay away from things like <a href="https://django-pipeline.readthedocs.io/en/latest/">Django Pipeline</a>.</p></li><li><p>Stay as close as possible from the real thing. Using ES6+ is nice because it's a superset of previous JS versions, so you can see transpiling as a simple layer of compatibility. I wouldn't recommend however to transpile Dart or Python or anything like that into JS.</p></li><li><p>Only do it if it brings an actual value for your daily work. By example, CoffeeScript is just an obfuscated version of JavaScript so it's probably not worth the pain, while something like Stylus/LESS/Sass bring variables and mixins to CSS will help you <em>a lot</em> to maintain CSS code.</p></li></ul><p>You're the judge of good transpilers for your projects. Just don't clutter yourself with useless tools that are not worth your time.</p><h2>9 &#8212; Limit pointer use to a single dereference, and do not use function pointers.</h2><blockquote><p><strong>Original rule</strong> &#8212; The use of pointers should be restricted. Specifically, no more than one level of dereferencing is allowed. Pointer dereference operations may not be hidden in macro definitions or inside typedef declarations. Function pointers are not permitted.</p></blockquote><p>Anybody who's done C beyond the basic examples will know the headache of pointers. It's like inception but with computer memory, you don't really know how deep you should follow the pointers.</p><p>The need for that is, by example, the <code>qsort()</code> function. You want to be able to sort any type of data but without knowing anything on them before compiling. Have a look at the signature:</p><pre><code>void qsort( void *ptr, size_t count, size_t size,
            int (*comp)(const void *, const void *) );</code></pre><p>It's one if the most frighteningly unsafe things you'll ever see in a standard library documentation. Yet, it allows the standard library to sort any kind of data, which other more modern language still have <a href="https://gobyexample.com/sorting">a little bit awkward</a> solutions.</p><p>But of course when you open the gate for this kind of things, you open the gate to any kind of pointer madness. And as you know, when a gate is open then people will go through it. Hence this rule for C.</p><p>However what about our case of interpreted languages? We will first cover why references are bad and then we will explain how to accomplish the initial intent of writing generic code.</p><h3>Don't use references</h3><p>Pointers don't exist but some ancient and obscure languages like <a href="https://www.php.net/manual/en/language.references.pass.php">PHP</a> still thought that it would be a good idea to have it. However, most of the other languages will only use a strategy named <a href="https://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_sharing">call-by-sharing</a>. The idea is &#8212; very quickly &#8212; that instead of passing a reference you will pass objects that can modify themselves.</p><p>The core point against references is that, beyond being memory unsafe and crazy in C, they also produce side-effects. By example, in PHP:</p><pre><code>function read($source, &amp;$n) {
    $content = // some way to get the content
    $n = // some way to get the read length

    return $content;
}

$n = 0;
$content = read("foo", $n);

print($n);</code></pre><p>That's a common, C-inspired, use-case for references. However, what you really want to do in this case is</p><pre><code>function read($source) {
    $content = // some way to get the content
    $n = // some way to get the read length

    return [$content, $n];
}

list($content, $n) = read("foo");

print($n);</code></pre><p>All you need is two return values instead of one. You can also return data objects which can fit any information you want them to fit and also evolve in the future without breaking existing code.</p><p>And all of this without affecting the scope of the calling function, which is rather nice.</p><p>Another safety point though is when you're modifying an object then you're potentially affecting the other users of that object. That's by example <a href="https://stackoverflow.com/questions/30979178/how-do-i-work-around-mutability-in-moment-js">a common pitfall of Moment.js</a>. Let's see.</p><pre><code>function add(obj, attr, value) {
    obj[attr] = (obj[attr] || 0) + value;
    return obj;
}

const a = {foo: 1};
const b = add(a, "foo", 1);

console.log(a.foo); // 2
console.log(b.foo); // 2</code></pre><p>On the other hand you can do:</p><pre><code>function add(obj, attr, value) {
    const patch = {};
    patch[attr] = (obj[attr] || 0) + value;
    return Object.assign({}, obj, patch);
}

const a = {foo: 1};
const b = add(a, "foo", 1);

console.log(a.foo); // 1
console.log(b.foo); // 2</code></pre><p>Both <code>a</code> and <code>b</code> stay distinct objects with distinct values because the <code>add()</code> function did a copy of <code>a</code> before returning it.</p><p>Let's conclude this already-too-long section with the final form of the rule:</p><blockquote><p>Don't mutate your arguments unless the explicit goal of your function is to mutate your arguments. If you do so, do it by sharing and not by reference.</p></blockquote><p>That would by example be the <a href="https://eslint.org/docs/rules/no-param-reassign">no-param-reassign</a> rule in ESLint as well as the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze">Object.freeze()</a> method. Or in Python you can use a <a href="https://docs.python.org/3/library/typing.html#typing.NamedTuple">NamedTuple</a> in many cases.</p><p>Note on performance: if you change the size of an object then the underlying process will basically be to allocate a new contiguous region of memory for it <a href="https://en.cppreference.com/w/c/memory/realloc">and then copy it</a>. For this reason, a mutation is often a copy anyways, so don't worry about copying your objects.</p><h3>Leverage the weak-ish dynamic typing</h3><p>Now that we closed the crazy door of references, we still need to write generic code if we want to stay <a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself">DRY</a>.</p><p>The good news is that while compiled languages are bound by the rules of physics and the way computers work, interpreted languages can have the luxury of putting a lot of additional support logic on top of that.</p><p>Specifically, they mostly rely on <a href="https://en.wikipedia.org/wiki/Duck_typing">duck typing</a>. Of course you can add some level of static type checking like <a href="https://www.typescriptlang.org/">TypeScript</a>, Python's <a href="https://docs.python.org/3/library/typing.html">type hints</a> or PHP's <a href="https://www.php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration">type declarations</a>. Using the wisdom of other rules:</p><ul><li><p>Rule 5 &#8212; Make many assertions. Expecting something from an object which doesn't actually have it will raise an exception, which you can catch and report.</p></li><li><p>Rule 10 &#8212; No warnings allowed (explained hereafter). Using the various type checking mechanisms you can rely on a static analyzer to help you spot errors that would arise at runtime.</p></li></ul><p>Those two rules will protect you from writing dangerous generic code. Which would result in the following rule</p><blockquote><p>You can write generic code as long as you use as many tools as possible to catch mistakes, and especially you need to follow rules 5 and 10.</p></blockquote><h2>10 &#8212; Compile with all possible warnings active; all warnings should then be addressed before release of the software.</h2><p>The initial full rule is:</p><blockquote><p>All code must be compiled, from the first day of development, with allcompiler warnings enabled at the compiler&#8217;s most pedantic setting. All code must compile with these setting without any warnings. All code must be checked daily with at least one, but preferably more than one, state-of-the-art static source code analyzer and should pass the analyses with zero warnings.</p></blockquote><p>Of course, interpreted code is not necessarily compiled so it's not about the compiler warnings <em>per se</em> but rather about getting the warnings.</p><p>There is fortunately a great amount of warning sources out there:</p><ul><li><p>All the <a href="https://www.jetbrains.com/idea/">JetBrains IDEs</a> are pretty awesome at finding out issues in your code. Recently, those IDE taught me a lot of patterns in different languages. That's really the main reason why I prefer something like this to a simplistic code editor: the warnings are very smart and helpful.</p></li><li><p>Linters for all the languages</p><ul><li><p>JavaScript &#8212; <a href="https://eslint.org/">eslint</a> with a set of rules <a href="https://github.com/airbnb/javascript">AirBnB</a> maybe?</p></li><li><p>Python &#8212; You can go full steam on <a href="https://github.com/astral-sh/ruff">Ruff</a> and pick the rules that suit you</p></li></ul></li><li><p>Automated code review tools like <a href="https://www.sonarqube.org/">SonarQube</a></p></li><li><p>Spell checkers are also surprisingly important because they will allow you to sniff out typos regardless of type analysis or any complicated static code analysis. It's a really efficient way to not lose hours because you typed <code>reuslts</code> instead of <code>results</code>.</p></li></ul><p>The main thing about warnings is that you <strong>must</strong> train your brain to see them. A single warning in the IDE will drive me mad while on the other hand I know people that just <em>won't see</em> them.</p><p>A final point on warnings is that on the contrary of compiled languages, warnings here are not always 100% certain. They are more like 95% certain and sometimes it's just an IDE bug. In that case, you should explicitly disable the warning and if possible give a small explanation of why you're sure that you don't need to apply this warning. However, think well before doing so because usually the IDE is right.</p><h2>Key takeaways</h2><p>The long discussion above tells us that those 10 rules were made for C and while you can use there philosophy in interpreted languages you can't really translate them into 10 other rules directly. Let's make our new power of 10 + 2 rules for interpreted languages.</p><ul><li><p><strong>Rule 1</strong> &#8212; Don't use <code>goto</code>, rationalize the use of <code>continue</code> and <code>break</code>, use <code>match</code> instead of <code>switch</code>.</p></li><li><p><strong>Rule 2</strong> &#8212; Prove that your problem can never create runaway code.</p></li><li><p><strong>Rule 3</strong> &#8212; To do so, limit the size of it. Usually using pagination, map/reduce, chunking, etc.</p></li><li><p><strong>Rule 4</strong> &#8212; Make code that fits in your head. If it fits in a page, it fits in your head.</p></li><li><p><strong>Rule 5</strong> &#8212; Check that things are right. Fail when wrong. Monitor failures. See rule 7.</p></li><li><p><strong>Rule 6</strong> &#8212; Don't use global-ish variables. Store data in the smallest possible scope.</p></li><li><p><strong>Rule 7</strong> &#8212; Let exceptions bubble up until you properly recover and/or report them.</p></li><li><p><strong>Rule 8</strong> &#8212; If you use transpilers, make sure that they solve more problems than they bring</p></li><li><p><strong>Rule 9.1</strong> &#8212; Don't use references even if your language supports it</p></li><li><p><strong>Rule 9.2</strong> &#8212; Copy arguments instead of mutating them, unless it's the explicit purpose of the function</p></li><li><p><strong>Rule 9.3</strong> &#8212; Use as many type-safety features as you can</p></li><li><p><strong>Rule 10</strong> &#8212; Use several linters and tools to analyze your code. No warning shall be ignored.</p></li></ul><p>And if you take a step back, all of those rules could be summed up in one rule to rule them all.</p><blockquote><p>Your computer, your RAM, your hard drive even your brain are bound by limits. You need to cut your problems, code and data into small boxes that will fit your computer, RAM, hard drive and brain. And that will fit together.</p></blockquote><p>&#8212; <em><s>Morpheus</s> Me</em></p><p>I consider that to be the core rule of programming and I apply it as an universal rationale to everything I do which is computer-related.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.baby-cto.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Baby CTO is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item></channel></rss>