How to Understand a Legacy Codebase Using AI Before Changing it — Opportunihub
Course Remote

How to Understand a Legacy Codebase Using AI Before Changing it

Hugo Teijiz · Remote

At a glance

Type
Course
Organisation
Hugo Teijiz
Location
Remote
Work mode
Remote
Deadline
Rolling / not stated
Posted
21 Aug 2026

About this course

<p>The first thing many engineers want to do when they inherit a legacy codebase is change it. And I understand the impulse.</p> <p>You open a class that's 1,500 lines long. There are database calls mixed with business rules, configuration values scattered across the repository, methods nobody wants to touch, and comments that refer to systems that disappeared years ago.</p> <p>Then an AI coding assistant offers to explain the whole thing.</p> <p>So you ask:</p> <blockquote> <p>Refactor this class.</p> </blockquote> <p>But that's usually too early.</p> <p>One of the lessons I've learned from working with legacy systems is that code can be ugly and still contain important knowledge.</p> <p>A strange condition may encode a business exception. A duplicated calculation may exist because two processes that look identical aren't actually identical. A database column with a terrible name may still be part of an external contract.</p> <p>And a method nobody understands may be the only thing preventing a production incident that happened eight years ago from happening again.</p> <p>AI makes it much easier to read unfamiliar software, and that's valuable. But it also makes it much easier to change software before you understand it.</p> <p>In this tutorial, I'll show you how to use AI for something I believe should happen before refactoring or migration: <strong>codebase archaeology.</strong></p> <p>You'll learn how to use AI to help you:</p> <ul> <li><p>map a repository,</p> </li> <li><p>identify entry points,</p> </li> <li><p>trace dependencies,</p> </li> <li><p>separate business rules from infrastructure,</p> </li> <li><p>find hidden side effects,</p> </li> <li><p>inspect data flow,</p> </li> <li><p>discover implicit contracts,</p> </li> <li><p>detect duplicated behavior,</p> </li> <li><p>build a dependency map,</p> </li> <li><p>identify areas of uncertainty,</p> </li> <li><p>and turn those findings into a modernization plan.</p> </li> </ul> <p>The examples use TypeScript, but the process works with most languages and stacks.</p> <p>The goal isn't to ask AI what the code means and trust the answer. The goal is to use AI to reduce the amount of time you spend looking for the right questions.</p> <h2 id="heading-prerequisites">Prerequisites</h2> <p>You should be comfortable with:</p> <ul> <li><p>reading an existing codebase</p> </li> <li><p>TypeScript or a similar object-oriented language</p> </li> <li><p>basic software architecture</p> </li> <li><p>dependency injection</p> </li> <li><p>unit and integration testing</p> </li> <li><p>using an AI coding assistant that can inspect repository files</p> </li> </ul> <p>You don't need a specific AI provider, as the workflow matters more than the model.</p> <h2 id="heading-table-of-contents">Table of Contents</h2> <ul> <li><p><a href="#heading-prerequisites">Prerequisites</a></p> </li> <li><p><a href="#heading-why-understanding-has-to-come-before-refactoring">Why Understanding Has to Come Before Refactoring</a></p> </li> <li><p><a href="#heading-how-to-start-with-the-repository-not-the-classes">How to Start with the Repository, Not the Classes</a></p> </li> <li><p><a href="#heading-how-to-find-the-real-entry-points">How to Find the Real Entry Points</a></p> </li> <li><p><a href="#heading-how-to-trace-a-business-capability-through-the-codebase">How to Trace a Business Capability Through the Codebase</a></p> </li> <li><p><a href="#heading-how-to-separate-business-rules-from-infrastructure">How to Separate Business Rules from Infrastructure</a></p> </li> <li><p><a href="#heading-how-to-find-hidden-side-effects">How to Find Hidden Side Effects</a></p> </li> <li><p><a href="#heading-how-to-discover-implicit-contracts">How to Discover Implicit Contracts</a></p> </li> <li><p><a href="#heading-how-to-use-ai-to-find-duplicated-business-rules">How to Use AI to Find Duplicated Business Rules</a></p> </li> <li><p><a href="#heading-how-to-build-a-lightweight-dependency-map">How to Build a Lightweight Dependency Map</a></p> </li> <li><p><a href="#heading-how-to-mark-what-you-still-do-not-understand">How to Mark What You Still Do Not Understand</a></p> </li> <li><p><a href="#heading-how-to-validate-ai-findings-against-the-system">How to Validate AI Findings Against the System</a></p> </li> <li><p><a href="#heading-how-to-turn-codebase-understanding-into-a-migration-plan">How to Turn Codebase Understanding into a Migration Plan</a></p> </li> <li><p><a href="#heading-a-practical-codebase-archaeology-workflow">A Practical Codebase Archaeology Workflow</a></p> </li> <li><p><a href="#heading-what-i-would-not-ask-ai-to-do-first">What I Would Not Ask AI to Do First</a></p> </li> <li><p><a href="#heading-the-most-useful-ai-output-is-sometimes-a-question">The Most Useful AI Output Is Sometimes a Question</a></p> </li> <li><p><a href="#heading-conclusion">Conclusion</a></p> </li> </ul> <h2 id="heading-why-understanding-has-to-come-before-refactoring">Why Understanding Has to Come Before Refactoring</h2> <p>Legacy code often creates a false sense of urgency.</p> <p>You see something obviously coupled or duplicated and immediately want to clean it up.</p> <p>Consider this function:</p> <pre><code class="language-typescript">async function approveOrder(order: Order) { if (order.total &gt; 10000 &amp;&amp; !order.customer.verified) { throw new Error("Manual verification required"); } if ( order.customer.country === "AR" &amp;&amp; order.paymentMethod === "TRANSFER" ) { order.status = "PENDING"; } else { order.status = "APPROVED"; } await orders.save(order); if (order.status === "APPROVED") { await billing.createInvoice(order); } await audit.log({ action: "ORDER_APPROVAL", orderId: order.id, status: order.status, }); return order; } </code></pre> <p>At first glance, there are several clear refactoring opportunities:</p> <ul> <li><p>You could extract validation.</p> </li> <li><p>You could isolate status calculation.</p> </li> <li><p>You could move billing behind an interface.</p> </li> <li><p>You could create an approval policy.</p> </li> </ul> <p>All of those ideas may be reasonable, but there are questions you should answer first:</p> <ul> <li><p>Why is <code>10000</code> important?</p> </li> <li><p>Why does an Argentine bank transfer remain pending?</p> </li> <li><p>Does invoice creation have to happen after persistence?</p> </li> <li><p>Is <code>ORDER_APPROVAL</code> consumed by another system?</p> </li> <li><p>Can orders transition from <code>PENDING</code> to <code>APPROVED</code> somewhere else?</p> </li> <li><p>Does anything depend on the exact exception message?</p> </li> </ul> <p>You can't answer those questions from syntax alone.</p> <p>That's where understanding begins.</p> <p>Instead of asking your AI tool:</p> <pre><code class="language-text">Refactor this function using clean architecture. </code></pre> <p>start with:</p> <pre><code class="language-text">Analyze this function without changing it. Identify: 1. explicit business rules, 2. likely business rules that need confirmation, 3. side effects, 4. external dependencies, 5. state transitions, 6. magic values, 7. assumptions that cannot be proven from this file alone. Do not propose a refactor yet. </code></pre> <p>That last line is important: <strong>Do not propose a refactor yet.</strong></p> <p>You want the model in investigation mode, not solution mode.</p> <h2 id="heading-how-to-start-with-the-repository-not-the-classes">How to Start with the Repository, Not the Classes</h2> <p>When I approach an unfamiliar legacy system, I don't start by reading every file. I start by trying to understand the shape of the application.</p> <p>A repository already contains architectural clues.</p> <p>Look for directories such as:</p> <pre><code class="language-text">src/ controllers/ services/ repositories/ models/ jobs/ workers/ scripts/ migrations/ config/ integrations/ tests/ </code></pre> <p>But don't assume the directory names describe the real architecture.</p> <p>A directory called <code>services</code> can contain business logic, infrastructure, orchestration, and random utility functions.</p> <p>A directory called <code>models</code> might contain database entities rather than domain models.</p> <p>A folder called <code>utils</code> can hide half the application's business logic.</p> <p>Use the structure as evidence, not truth.</p> <p>A useful first AI request is:</p> <pre><code class="language-text">Inspect the repository structure. Do not analyze individual implementation details yet. Identify: - application entry points, - major modules, - database technologies, - external integrations, - background processing, - scheduled tasks, - authentication mechanisms, - configuration sources, - tests, - likely architectural boundaries. For each conclusion, reference the files or directories that support it. Mark anything uncertain explicitly. </code></pre> <p>The requirement to reference files matters. Without it, AI can give you a perfectly reasonable architecture that doesn't actually exist.</p> <p>You want something closer to:</p> <pre><code class="language-text">HTTP API Evidence: - src/server.ts - src/routes/orders.ts - src/routes/customers.ts Background processing Evidence: - src/workers/paymentWorker.ts - src/queues/index.ts Scheduled jobs Evidence: - src/jobs/reconcileInvoices.ts - src/cron.ts </code></pre> <p>Now you have a map you can verify.</p> <h2 id="heading-how-to-find-the-real-entry-points">How to Find the Real Entry Points</h2> <p>Web applications often have an obvious HTTP entry point. But legacy systems frequently have several more.</p> <p>A business operation may begin from:</p> <ul> <li><p>an API request,</p> </li> <li><p>a scheduled job,</p> </li> <li><p>a queue consumer,</p> </li> <li><p>a database trigger,</p> </li> <li><p>a CLI script,</p> </li> <li><p>a file import,</p> </li> <li><p>an email handler,</p> </li> <li><p>a webhook,</p> </li> <li><p>or another application calling the database directly.</p> </li> </ul> <p>If you only analyze controllers, you may miss half the system.</p> <p>Suppose you search for order creation and find:</p> <pre><code class="language-text">POST /orders </code></pre> <p>It would be easy to assume that all orders enter through that endpoint.</p> <p>Then you discover:</p> <pre><code class="language-text">jobs/importMarketplaceOrders.ts workers/retryFailedOrders.ts scripts/migratePendingOrders.ts integrations/shopify/webhook.ts </code></pre> <p>Now the same business object has four additional entry paths.</p> <p>This changes how you think about refactoring.</p> <p>Ask AI:</p> <pre><code class="language-text">Find every location that can create, modify, approve, cancel, or persist an Order. Include: - HTTP endpoints, - background workers, - scheduled jobs, - scripts, - imports, - webhooks, - direct repository calls. Group the results by operation. For every result, include the file path and the relevant function or class. </code></pre> <p>Then verify those results with repository search.</p> <p>For example:</p> <pre><code class="language-bash">rg "orders\.save|orders\.insert|createOrder|approveOrder" src </code></pre> <p>AI should accelerate search, not replace it.</p> <h2 id="heading-how-to-trace-a-business-capability-through-the-codebase">How to Trace a Business Capability Through the Codebase</h2> <p>Understanding individual files isn't enough.</p> <p>What usually matters is understanding a <strong>business capability</strong>.</p> <p>For example:</p> <blockquote> <p>Create an order.</p> </blockquote> <p>That capability may travel through several layers:</p> <pre><code class="language-text">HTTP Request ↓ Controller ↓ Application Service ↓ Pricing ↓ Inventory ↓ Persistence ↓ Payment ↓ Notification </code></pre> <p>The code may not be organized that cleanly, and that's precisely why tracing the capability is useful.</p> <p>Choose one real workflow and ask:</p> <pre><code class="language-text">Trace the "Create Order" capability from its entry point until all observable side effects are complete. For each step, show: - file, - function or class, - input, - output, - state change, - external call, - error behavior. Do not summarize multiple steps into one. </code></pre> <p>You want a sequence that you can inspect.</p> <p>For example:</p> <pre><code class="language-text">1. POST /orders src/routes/orders.ts 2. OrdersController.create() src/controllers/OrdersController.ts 3. OrderService.create() src/services/OrderService.ts 4. calculatePrice() src/services/pricing.ts 5. inventory.reserve() src/integrations/inventory.ts 6. ordersRepository.save() src/repositories/orders.ts 7. paymentQueue.publish() src/queues/payment.ts </code></pre> <p>This becomes far more useful than a generic explanation of the architecture.</p> <p>Now you can ask questions such as:</p> <ul> <li><p>Where does the transaction actually begin?</p> </li> <li><p>What happens if payment publishing fails?</p> </li> <li><p>Is inventory reservation reversible?</p> </li> <li><p>Can the order be saved twice?</p> </li> <li><p>Which steps are synchronous?</p> </li> <li><p>Which failures are retried?</p> </li> </ul> <p>Those are modernization questions.</p> <h2 id="heading-how-to-separate-business-rules-from-infrastructure">How to Separate Business Rules from Infrastructure</h2> <p>One of the most useful things you can do during codebase archaeology is identify where business behavior lives.</p> <p>Legacy applications frequently mix it with infrastructure.</p> <p>Consider:</p> <pre><code class="language-typescript">async function saveCustomer(customer: Customer) { if ( customer.type === "ENTERPRISE" &amp;&amp; customer.creditLimit &lt; 50000 ) { throw new Error("Invalid enterprise credit limit"); } const connection = await mysql.getConnection(); await connection.query( "INSERT INTO customers (...) VALUES (...)", [...] ); await redis.del(`customer:${customer.id}`); await eventBus.publish( "customer.updated", customer ); } </code></pre> <p>There's at least one business rule:</p> <pre><code class="language-text">Enterprise customers must have a credit limit &gt;= 50000. </code></pre> <p>And several infrastructure concerns:</p> <pre><code class="language-text">MySQL Redis Event bus </code></pre> <p>Ask AI to classify the code:</p> <pre><code class="language-text">Classify each responsibility in this function as one of: - business rule, - application orchestration, - persistence, - caching, - messaging, - logging, - validation, - unknown. Explain why. Do not move or rewrite any code. </code></pre> <p>The <code>unknown</code> category is useful. You don't want the model to force every line into a clean architectural theory.</p> <p>Some code really is ambiguous until you inspect more context.</p> <h2 id="heading-how-to-find-hidden-side-effects">How to Find Hidden Side Effects</h2> <p>Side effects are one of the biggest sources of migration risk.</p> <p>A function called:</p> <pre><code class="language-typescript">updateCustomer() </code></pre> <p>may do much more than update a customer.</p> <p>It may:</p> <ul> <li><p>write to the database</p> </li> <li><p>invalidate cache</p> </li> <li><p>emit an event</p> </li> <li><p>send an email</p> </li> <li><p>update analytics</p> </li> <li><p>write an audit record</p> </li> <li><p>schedule another job</p> </li> </ul> <p>If you refactor the function and preserve only its return value, you can break production behavior without any compiler error.</p> <p>A useful investigation prompt is:</p> <pre><code class="language-text">List every observable side effect produced directly or indirectly by this function. For each one, identify: - the side effect, - where it happens, - whether it is synchronous or asynchronous, - whether failure propagates, - whether it appears retryable, - whether it is idempotent, - whether it can be safely repeated. Mark uncertain answers as unknown. </code></pre> <p>That last property, idempotency, matters a lot.</p> <p>Suppose a worker does this:</p> <pre><code class="language-typescript">await chargeCard(order); await markOrderAsPaid(order); </code></pre> <p>If the worker crashes between those two lines and retries, what happens? You may charge the customer twice. And that's not visible from the function name.</p> <p>Understanding retry semantics is part of understanding the codebase.</p> <h2 id="heading-how-to-discover-implicit-contracts">How to Discover Implicit Contracts</h2> <p>Not every contract is declared with an interface. Legacy applications contain many implicit contracts.</p> <p>For example:</p> <pre><code class="language-typescript">return { status: "ok", value: customer.balance.toFixed(2), }; </code></pre> <p>Some external consumer may depend on:</p> <pre><code class="language-json">{ "status": "ok", "value": "100.00" } </code></pre> <p>Changing <code>value</code> from a string to a number can look like an improvement:</p> <pre><code class="language-json">{ "status": "ok", "value": 100 } </code></pre> <p>It can also break a client.</p> <p>Look for contracts in:</p> <ul> <li><p>API responses,</p> </li> <li><p>events,</p> </li> <li><p>database structures,</p> </li> <li><p>CSV exports,</p> </li> <li><p>filenames,</p> </li> <li><p>environment variables,</p> </li> <li><p>error messages,</p> </li> <li><p>queue payloads,</p> </li> <li><p>and webhook bodies.</p> </li> </ul> <p>Ask:</p> <pre><code class="language-text">Identify outputs from this module that could be consumed outside the module. Include: - HTTP responses, - emitted events, - queue messages, - files, - database records, - exceptions, - logs used for automated processing. For each output, explain what evidence suggests that it may be an external or implicit contract. </code></pre> <p>The wording matters:</p> <blockquote> <p>what evidence suggests</p> </blockquote> <p>not:</p> <blockquote> <p>tell me which contracts exist</p> </blockquote> <p>because you may not be able to prove the consumer from the current repository.</p> <h2 id="heading-how-to-use-ai-to-find-duplicated-business-rules">How to Use AI to Find Duplicated Business Rules</h2> <p>Duplicated code is easy to detect. Duplicated <strong>business meaning</strong> is harder.</p> <p>You may find:</p> <pre><code class="language-typescript">if (customer.type === "PREMIUM") { discount = total * 0.1; } </code></pre> <p>in one module.</p> <p>And elsewhere:</p> <pre><code class="language-typescript">if (account.plan === "GOLD") { price = price * 0.9; } </code></pre> <p>Those might represent the same business rule, or they might not.</p> <p>AI is useful for identifying candidates.</p> <p>Ask:</p> <pre><code class="language-text">Search the repository for business rules related to customer discounts. Group implementations that appear semantically related, even if variable names differ. For each group: - list file locations, - describe the apparent rule, - highlight differences, - do not assume the rules should be unified. </code></pre> <p>That final instruction is important.</p> <p>Duplication is sometimes accidental.</p> <p>Sometimes it represents two domains that evolved independently.</p> <p>Don't let an AI assistant turn:</p> <pre><code class="language-text">similar </code></pre> <p>into:</p> <pre><code class="language-text">must be merged </code></pre> <p>without evidence.</p> <h2 id="heading-how-to-build-a-lightweight-dependency-map">How to Build a Lightweight Dependency Map</h2> <p>At some point, you need to understand which parts of the system depend on which others.</p> <p>You don't need a perfect enterprise architecture diagram. A lightweight dependency map is enough to start.</p> <p>For example:</p> <pre><code class="language-text">Orders ├── Customers ├── Inventory ├── Payments ├── Notifications └── Database Payments ├── Payment Provider ├── Audit └── Database </code></pre> <p>Ask AI to extract module-level dependencies:</p> <pre><code class="language-text">Build a module dependency map from the repository. Only include dependencies supported by imports, constructor dependencies, explicit calls, or configuration. Output: Module A -&gt; Module B For each dependency, provide at least one source file that demonstrates it. Do not infer dependencies from names alone. </code></pre> <p>You can then compare the result with automated tools.</p> <p>For JavaScript or TypeScript projects, dependency analysis tools can help you find:</p> <ul> <li><p>circular dependencies</p> </li> <li><p>cross-module imports</p> </li> <li><p>high fan-in</p> </li> <li><p>high fan-out</p> </li> </ul> <p>AI is useful for explaining why those dependencies may matter. Static analysis is better at proving that they exist.</p> <p>Use both.</p> <h2 id="heading-how-to-mark-what-you-still-do-not-understand">How to Mark What You Still Do Not Understand</h2> <p>This is one of the most important parts of the process.</p> <p>A useful system map doesn't only contain answers. It also contains uncertainty.</p> <p>I like keeping an explicit list such as:</p> <pre><code class="language-markdown">## Open Questions - Why is the enterprise credit threshold 50,000? - Is `ORDER_APPROVAL` consumed outside this repository? - Can marketplace orders bypass inventory validation? - Is `customer.balance` allowed to be negative? - What process transitions PENDING orders to APPROVED? - Is `legacy_customer_id` still used by another system? </code></pre> <p>You can ask AI to generate this list:</p> <pre><code class="language-text">Based on everything analyzed so far, list the questions that can't be answered safely from the repository. Focus on questions that would matter during: - refactoring, - migration, - schema changes, - interface changes, - removal of code. Do not answer the questions. </code></pre> <p>I like this prompt because it does the opposite of what we normally ask AI to do. It asks the model to identify where it should <strong>not</strong> pretend to know.</p> <p>A modernization plan should include those unknowns.</p> <h2 id="heading-how-to-validate-ai-findings-against-the-system">How to Validate AI Findings Against the System</h2> <p>AI-generated explanations can sound convincing even when they're incomplete. So every important finding should have another source of evidence.</p> <p>I use a simple hierarchy.</p> <h3 id="heading-repository-search">Repository Search</h3> <p>If AI says a function is called only once, search for it.</p> <pre><code class="language-bash">rg "approveOrder" . </code></pre> <h3 id="heading-tests">Tests</h3> <p>Tests often reveal assumptions that implementation code doesn't explain.</p> <p>Look for:</p> <pre><code class="language-text">expected errors special values boundary cases fixture data historical behavior </code></pre> <h3 id="heading-database-schema">Database Schema</h3> <p>The schema may reveal key things like:</p> <ul> <li><p>nullable fields</p> </li> <li><p>foreign keys</p> </li> <li><p>defaults</p> </li> <li><p>legacy columns</p> </li> <li><p>constraints</p> </li> <li><p>status values</p> </li> </ul> <h3 id="heading-logs-and-observability">Logs and Observability</h3> <p>Production telemetry can tell you whether a supposedly unused path is still active.</p> <h3 id="heading-version-history">Version History</h3> <p>Git history can sometimes answer questions that source code can't.</p> <p>For example:</p> <pre><code class="language-bash">git log -S "Manual verification required" --all </code></pre> <p>or:</p> <pre><code class="language-bash">git blame src/orders/approveOrder.ts </code></pre> <p>The commit that introduced a strange condition may contain the explanation.</p> <p>This is an area where AI can help summarize history:</p> <pre><code class="language-text">Review the commits that changed this function. Build a timeline of behavior changes. For each change, include: - commit, - date, - behavior changed, - stated reason if available. Do not infer a reason if the commit history does not provide one. </code></pre> <p>That can save a surprising amount of time.</p> <h2 id="heading-how-to-turn-codebase-understanding-into-a-migration-plan">How to Turn Codebase Understanding into a Migration Plan</h2> <p>Once you understand one capability, you can begin making decisions. But not before.</p> <p>Suppose your investigation produces this:</p> <pre><code class="language-text">Create Order Business rules: - active customer required - premium customers receive 10% discount - inventory must be available Side effects: - order persisted - inventory reserved - payment queued - confirmation email sent External contracts: - POST /orders response - payment queue payload - order.created event Unknowns: - retry semantics for inventory reservation - whether event consumers require exact field names </code></pre> <p>Now you can decide what to protect.</p> <p>For example:</p> <pre><code class="language-text">Protect first: - pricing behavior - API response - payment payload - event schema </code></pre> <p>Then decide what can be refactored.</p> <pre><code class="language-text">Candidate boundaries: - pricing policy - inventory gateway - payment publisher - notification service </code></pre> <p>Then decide what needs investigation.</p> <pre><code class="language-text">Block migration until understood: - inventory retry behavior - event consumers </code></pre> <p>That's already a migration plan.</p> <p>Notice what AI did not do: it didn't decide the target architecture.</p> <p>It helped make the current architecture observable enough for you to make that decision.</p> <h2 id="heading-a-practical-codebase-archaeology-workflow">A Practical Codebase Archaeology Workflow</h2> <p>If I had to reduce this process to something repeatable, I would use these steps.</p> <h3 id="heading-1-map-the-repository">1. Map the Repository</h3> <p>Identify:</p> <ul> <li><p>entry points</p> </li> <li><p>modules</p> </li> <li><p>persistence</p> </li> <li><p>integrations</p> </li> <li><p>workers</p> </li> <li><p>jobs</p> </li> <li><p>tests</p> </li> <li><p>configuration</p> </li> </ul> <p>Don't refactor anything.</p> <h3 id="heading-2-choose-one-capability">2. Choose One Capability</h3> <p>Pick something concrete:</p> <pre><code class="language-text">Create Order Approve Loan Generate Invoice Register Customer Cancel Subscription </code></pre> <p>Avoid trying to understand the whole product at once.</p> <h3 id="heading-3-trace-it-end-to-end">3. Trace It End to End</h3> <p>Follow:</p> <pre><code class="language-text">input ↓ business logic ↓ state changes ↓ external calls ↓ output </code></pre> <p>Record every file involved.</p> <h3 id="heading-4-extract-business-rules">4. Extract Business Rules</h3> <p>Separate:</p> <ul> <li><p>explicit rules</p> </li> <li><p>likely rules</p> </li> <li><p>infrastructure behavior</p> </li> <li><p>unknowns</p> </li> </ul> <h3 id="heading-5-identify-side-effects">5. Identify Side Effects</h3> <p>Find:</p> <ul> <li><p>writes</p> </li> <li><p>messages</p> </li> <li><p>emails</p> </li> <li><p>jobs</p> </li> <li><p>cache changes</p> </li> <li><p>external calls</p> </li> </ul> <h3 id="heading-6-discover-contracts">6. Discover Contracts</h3> <p>Look for:</p> <ul> <li><p>APIs</p> </li> <li><p>event schemas</p> </li> <li><p>database assumptions</p> </li> <li><p>exported files</p> </li> <li><p>error behavior</p> </li> </ul> <h3 id="heading-7-map-dependencies">7. Map Dependencies</h3> <p>Document:</p> <pre><code class="language-text">module -&gt; module </code></pre> <p>and identify coupling.</p> <h3 id="heading-8-record-unknowns">8. Record Unknowns</h3> <p>Don't hide uncertainty. Create an explicit list.</p> <h3 id="heading-9-verify">9. Verify</h3> <p>Use:</p> <ul> <li><p>repository search</p> </li> <li><p>tests</p> </li> <li><p>schema</p> </li> <li><p>logs</p> </li> <li><p>Git history</p> </li> <li><p>production telemetry</p> </li> </ul> <h3 id="heading-10-only-then-plan-the-change">10. Only Then Plan the Change</h3> <p>Decide:</p> <ul> <li><p>what behavior must survive,</p> </li> <li><p>what code can disappear,</p> </li> <li><p>what boundaries should be introduced,</p> </li> <li><p>what needs tests,</p> </li> <li><p>and what can migrate first.</p> </li> </ul> <h2 id="heading-what-i-would-not-ask-ai-to-do-first">What I Would Not Ask AI to Do First</h2> <p>There are several prompts I avoid at the beginning of a legacy modernization project.</p> <p>For example:</p> <pre><code class="language-text">Rewrite this application using Clean Architecture. </code></pre> <p>or:</p> <pre><code class="language-text">Convert this monolith into microservices. </code></pre> <p>or:</p> <pre><code class="language-text">Modernize this entire repository. </code></pre> <p>or even:</p> <pre><code class="language-text">Find all the bad code. </code></pre> <p>The problem isn't that AI can't produce useful output from those prompts. It can.</p> <p>The problem is that those questions already contain a solution.</p> <p>You're asking for:</p> <pre><code class="language-text">Clean Architecture Microservices Rewrite Bad code </code></pre> <p>before you've established what the system actually needs.</p> <p>A better sequence is:</p> <pre><code class="language-text">What exists? ↓ Why does it exist? ↓ What behavior matters? ↓ What is uncertain? ↓ What should change? </code></pre> <p>That sequence is slower for the first hour, but it's usually much faster for the rest of the project.</p> <h2 id="heading-the-most-useful-ai-output-is-sometimes-a-question">The Most Useful AI Output Is Sometimes a Question</h2> <p>There's a tendency to evaluate AI coding tools by how much code they generate.</p> <p>For legacy systems, I think that misses part of their value.</p> <p>One of the most useful outputs can be:</p> <blockquote> <p>I cannot determine why this condition exists from the available code.</p> </blockquote> <p>Or:</p> <blockquote> <p>This event appears to have no consumer in the current repository, but external consumers cannot be ruled out.</p> </blockquote> <p>Or:</p> <blockquote> <p>These two discount calculations look similar, but their behavior differs for zero-value orders.</p> </blockquote> <p>Those are useful findings that tell an engineer where to investigate.</p> <p>A confident but incorrect answer is much more dangerous.</p> <p>When working with legacy systems, uncertainty is information. Treat it that way.</p> <h2 id="heading-conclusion">Conclusion</h2> <p>AI makes unfamiliar codebases much easier to explore.</p> <p>You can use it to summarize modules, trace execution paths, extract candidate business rules, find side effects, compare implementations, analyze Git history, and build dependency maps.</p> <p>That can remove a large amount of mechanical investigation work.</p> <p>But understanding a system isn't the same as generating an explanation of it. Legacy applications contain context that may exist outside the source code:</p> <ul> <li><p>production behavior,</p> </li> <li><p>old incidents,</p> </li> <li><p>external consumers,</p> </li> <li><p>business exceptions,</p> </li> <li><p>undocumented integrations,</p> </li> <li><p>and organizational history.</p> </li> </ul> <p>AI can help you find evidence. It can't manufacture missing history.</p> <p>That's why I prefer to use it as an investigator before I use it as a transformer.</p> <p>Start with:</p> <pre><code class="language-text">What does this system actually do? </code></pre> <p>Then ask:</p> <pre><code class="language-text">What do I still not understand? </code></pre> <p>Only after that should you ask:</p> <pre><code class="language-text">What should I change? </code></pre> <p>The faster AI lets you modify software, the more important that sequence becomes.</p> <p>Because changing code you understand is engineering. But changing code you don't understand is experimentation.</p> <p>And production is usually the most expensive place to run that experiment.</p>

How to apply

  1. 1 Read the full details above and confirm you meet the eligibility criteria.
  2. 2 Prepare your documents — an updated CV, and any cover letter, proposal or certificates required.
  3. 3 Click Apply on official site to complete your application on Hugo Teijiz’s official page.
  4. 4 Submit as early as possible — many close once filled.
Apply on official site

Sourced from freecodecamp. Always verify details on the official website. Opportunihub never charges you to apply.

Frequently asked questions

How do I apply for How to Understand a Legacy Codebase Using AI Before Changing it?

Review the full details and eligibility on this page, prepare your documents, then use the “Apply on official site” button to complete your application on Hugo Teijiz’s official page.

Is this opportunity remote or location-based?

This opportunity is remote-friendly and open to applicants who can work from anywhere.

Is How to Understand a Legacy Codebase Using AI Before Changing it free to apply for?

Opportunihub lists this Course for free. Legitimate Courses do not ask for payment to apply — never pay a fee to submit an application.