<?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: OptiRefine</title>
    <description>The latest articles on DEV Community by OptiRefine (@optirefine_ce29a935fde277).</description>
    <link>https://dev.to/optirefine_ce29a935fde277</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%2F3901349%2F04911fe2-6b65-4bf4-a989-f306164b9636.png</url>
      <title>DEV Community: OptiRefine</title>
      <link>https://dev.to/optirefine_ce29a935fde277</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/optirefine_ce29a935fde277"/>
    <language>en</language>
    <item>
      <title>The Code Review Comment I Left 47 Times</title>
      <dc:creator>OptiRefine</dc:creator>
      <pubDate>Tue, 28 Apr 2026 00:33:22 +0000</pubDate>
      <link>https://dev.to/optirefine_ce29a935fde277/the-code-review-comment-i-left-47-times-1e7f</link>
      <guid>https://dev.to/optirefine_ce29a935fde277/the-code-review-comment-i-left-47-times-1e7f</guid>
      <description>&lt;h1&gt;
  
  
  The Code Review Comment I Left 47 Times
&lt;/h1&gt;

&lt;p&gt;I counted once. Not proud of it.&lt;/p&gt;

&lt;p&gt;Forty-seven times across different PRs, different engineers, different companies — I left some version of the same comment. It usually looked like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Hey — this nested loop is going to hurt you at scale. You're doing O(n²) work here. Can you use a set for the inner lookup instead?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sometimes the author got it immediately and fixed it in ten minutes. Sometimes I'd spend forty-five minutes in a thread explaining what O(n²) actually means in practice, with benchmarks, with examples, with a rewrite pasted directly into the comment. Sometimes the fix went in. Sometimes a slightly different version of the same pattern showed up in the next PR from the same person.&lt;/p&gt;

&lt;p&gt;And every single time, I thought the same thing: &lt;em&gt;this should not require a human.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Thing Nobody Talks About in Code Review
&lt;/h2&gt;

&lt;p&gt;Code review has this mythology around it. We talk about it like it's this high-value, high-skill practice where senior engineers pass down wisdom to junior ones. And sometimes it is. The architectural discussions, the "have you considered what happens when this service goes down" conversations, the stuff that requires genuine experience and judgment — that's worth a human's time.&lt;/p&gt;

&lt;p&gt;But a massive chunk of code review isn't that. It's pattern matching. It's the same ten categories of problem appearing in slightly different costumes, over and over, in every codebase, at every company. Nested loops. Hardcoded credentials. Unused variables. Functions so tangled with conditionals that you need a flowchart to test them. Files left open without context managers. List comprehensions written as manual loops for no reason.&lt;/p&gt;

&lt;p&gt;These aren't judgment calls. They have objectively correct answers. And yet we route them through humans anyway, which means they get caught inconsistently, they get caught late, and they generate the kind of review comments that quietly frustrate junior engineers who feel like they're failing when really they just weren't told the rules.&lt;/p&gt;

&lt;p&gt;I got tired of being part of that system. So I built something to replace the parts of it that shouldn't need me.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Actually Built
&lt;/h2&gt;

&lt;p&gt;OptiScan is a static analysis engine. You paste Python code in. It parses the code into a concrete syntax tree — not a string, not a token stream, an actual typed tree where every node is a specific kind of thing with specific kinds of children. Then it walks that tree and runs a set of analysis passes, each of which is looking for something specific.&lt;/p&gt;

&lt;p&gt;The output is not a suggestion. It's a rewrite.&lt;/p&gt;

&lt;p&gt;When the engine finds a nested loop doing pair comparisons — the O(n²) pattern I've left forty-seven comments about — it doesn't tell you it's bad. It replaces it. You get back working code that does the same thing in O(n) time using a HashSet for constant-time membership checks. You can copy it directly. You can run the benchmark in the browser to see the difference in milliseconds.&lt;/p&gt;

&lt;p&gt;That's the thing I wanted to get right. A linter that tells you your code is slow and then leaves you to figure out the fix is only half useful. The fix is the point.&lt;/p&gt;




&lt;h2&gt;
  
  
  Let Me Show You What I Mean
&lt;/h2&gt;

&lt;p&gt;Here's the function that ships as the default example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_pairs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&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;result&lt;/span&gt;

&lt;span class="n"&gt;dataset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;pairs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;find_pairs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clean enough. Readable. Does what it says. And for a dataset of 500 elements it probably runs fine on your laptop, which is exactly why it makes it to production.&lt;/p&gt;

&lt;p&gt;But feed it 50,000 elements and suddenly you're doing 1.25 billion comparison operations. Feed it a million elements and you've brought a service to its knees over a function that looked completely harmless in review.&lt;/p&gt;

&lt;p&gt;The AST engine parses this, detects the outer loop, detects the inner &lt;code&gt;For&lt;/code&gt; node as the only direct child of the outer loop body, confirms there's an &lt;code&gt;append&lt;/code&gt; call buried inside, identifies the collection name from the &lt;code&gt;range(len(arr))&lt;/code&gt; pattern, and rewrites:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;arr_set&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;complement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;complement&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;arr_set&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;complement&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One pass. O(1) set lookup. The benchmark in the browser — running both versions via Pyodide, a Python interpreter compiled to WebAssembly — shows this version running between 20 and 40 times faster on the default dataset. Not because I said so. Because you can run it yourself and watch the numbers.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Security Part That Kept Surprising People
&lt;/h2&gt;

&lt;p&gt;I added a security auditor because hardcoded secrets in source code is the other comment I've left too many times. But when I started building it I ran into something that made me realise most linters are only doing half the job.&lt;/p&gt;

&lt;p&gt;The obvious case is this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sk-abc123def456&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everyone knows that's bad. Most linters catch it. But what about this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1223&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a real thing people do. Numeric API keys, numeric tokens, numeric "passwords" that are just IDs they didn't want to hardcode as strings for some reason. Most static analysis tools only check for string literals in sensitive variable assignments because that's the obvious case. They're checking the wrong layer — they're looking at the &lt;em&gt;value type&lt;/em&gt; they expect rather than the &lt;em&gt;variable name pattern&lt;/em&gt; combined with &lt;em&gt;any literal at all&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The OptiScan security auditor checks both. It matches the variable name against a list of sensitive keywords — &lt;code&gt;api_key&lt;/code&gt;, &lt;code&gt;token&lt;/code&gt;, &lt;code&gt;secret&lt;/code&gt;, &lt;code&gt;password&lt;/code&gt;, &lt;code&gt;credentials&lt;/code&gt;, &lt;code&gt;stripe_key&lt;/code&gt;, &lt;code&gt;twilio_token&lt;/code&gt;, twenty-odd others — and then checks whether the assigned value is &lt;em&gt;any&lt;/em&gt; literal type: string, integer, float, all of it. If your variable name looks like it should be a secret, it shouldn't be assigned a literal. Ever. In any form.&lt;/p&gt;

&lt;p&gt;It also catches &lt;code&gt;eval()&lt;/code&gt; and &lt;code&gt;exec()&lt;/code&gt; calls, &lt;code&gt;subprocess&lt;/code&gt; with &lt;code&gt;shell=True&lt;/code&gt;, AWS Access Key IDs embedded in string literals via regex, and a handful of dangerous module imports. The report comes back with line numbers and specific remediation instructions, not just a flag.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cyclomatic Complexity Is One of Those Metrics That Sounds Boring Until You See It On Your Code
&lt;/h2&gt;

&lt;p&gt;I'll be honest — I almost didn't add this. Cyclomatic complexity feels like the kind of thing you put in an enterprise tool so project managers have something to put in a quarterly report.&lt;/p&gt;

&lt;p&gt;Then I ran it on some of my own old code.&lt;/p&gt;

&lt;p&gt;The score is simple: start at 1, add 1 for every branch point. Every &lt;code&gt;if&lt;/code&gt;. Every &lt;code&gt;for&lt;/code&gt;. Every &lt;code&gt;while&lt;/code&gt;. Every &lt;code&gt;except&lt;/code&gt;. Every &lt;code&gt;and&lt;/code&gt; and &lt;code&gt;or&lt;/code&gt; in a boolean expression. Functions under 5 are LOW — easy to reason about, easy to write tests for. 6–10 is MEDIUM — manageable but worth a look. 11 and above is HIGH — that function is doing too many things and the test surface is enormous.&lt;/p&gt;

&lt;p&gt;The thing about cyclomatic complexity is that it's not really about complexity. It's about testability. A function with a score of 14 has 14 independent paths through it. That's 14 test cases minimum for full branch coverage. In practice you're probably testing 3 of them and hoping the others behave. The score makes that problem visible in a way that reading the code doesn't always do.&lt;/p&gt;




&lt;h2&gt;
  
  
  Dead Code and Why It Accumulates
&lt;/h2&gt;

&lt;p&gt;Every codebase I've worked in for more than a year has dead code in it. Functions that were replaced but not removed. Variables assigned in an early version of a feature that got refactored away. Imports for libraries that no longer get called.&lt;/p&gt;

&lt;p&gt;It accumulates because removing it feels risky and finding it is annoying. You're never quite sure if something is &lt;em&gt;actually&lt;/em&gt; unused or if you just can't see where it's used. So you leave it. Then the next engineer leaves it. Then it's been there for three years and nobody knows what it does or whether removing it will break something.&lt;/p&gt;

&lt;p&gt;The dead code detector uses libcst's scope provider — actual scope analysis, not just text search — to track which names are defined and which names are referenced in the same module. Defined but never referenced means dead. It surfaces unused functions with their names, unused variable assignments, and unreachable code after return statements. Not heuristics. Not grep. Scope analysis.&lt;/p&gt;




&lt;h2&gt;
  
  
  On Building This Alone
&lt;/h2&gt;

&lt;p&gt;I want to say something about the solo build experience because I think it's relevant to the tool itself.&lt;/p&gt;

&lt;p&gt;When you're building something without a team, you have to be extremely honest about where the value actually is. You can't ship six half-finished features and call it a product. You have to pick the thing that is genuinely useful and make that thing work properly.&lt;/p&gt;

&lt;p&gt;For me that meant the analysis had to be deterministic. Not "pretty good most of the time." Deterministic. If the security auditor says there's a hardcoded secret, there's a hardcoded secret. If the complexity engine says this is O(n²), it's O(n²). If the rewriter produces code, that code has to actually run and be correct.&lt;/p&gt;

&lt;p&gt;That constraint forced some decisions that made the engine better. Using libcst instead of a language model for analysis. Testing the transformer output before marking anything as successfully converted — the bug where my transformer was silently deleting variable declarations because it marked a conversion as successful before checking if the conversion actually worked taught me that lesson very directly.&lt;/p&gt;

&lt;p&gt;The engine is real. It has rough edges I'm filing down. But what it does, it does correctly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where It Goes From Here
&lt;/h2&gt;

&lt;p&gt;GitHub PR integration is the one I'm working toward most urgently. Paste a pull request URL, get back an analysis of every Python file changed in the diff. That's the version that fits into a real engineering workflow without anyone having to change how they work.&lt;/p&gt;

&lt;p&gt;After that: more language support, team workspaces so organisations can share scan history and audit reports, and a webhook mode so you can trigger scans automatically on push and route results to wherever your team already lives.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://optirefine.qzz.io" rel="noopener noreferrer"&gt;optirefine.qzz.io&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Free tier available. No credit card. Paste something you've been staring at for too long and see what comes back.&lt;/p&gt;

&lt;p&gt;If the engine misses something it should catch, or produces a rewrite that's wrong, I want to know. The best way to improve static analysis tooling is to throw real-world code at it — the messy, inconsistent, underdocumented code that actually exists in production, not the clean examples from tutorials.&lt;/p&gt;

&lt;p&gt;That's the code that matters. That's what it's built for.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Solo-built. Actively maintained. Feedback welcome.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>python</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
