Faisalaffan Blog
|
🇮🇩
Faisalaffan Blog
Retrospektif18 menit baca

Tech Debt Rp 847 Juta/Tahun yang Tidak Ada yang Sadar — Cara Saya Menjual Refactoring ke Manajemen (dan Menang)

MVP cepat jadi, tapi sekarang setiap fitur baru butuh 3x lebih lama? Ini cerita nyata bagaimana tech debt diam-diam menggerogoti Rp 847 juta/tahun — dan framework yang saya pakai untuk meyakinkan manajemen bahwa refactoring adalah investasi, bukan cost.

Faisal Affan
Faisal Affan
Diterbitkan
Galeri
1 / 3

Refactoring & Compounding Tech Debt

"Move fast and break things. Unless you are breaking things faster than you can fix them." — Adapted from Mark Zuckerberg's later reflection

Konteks Artikel Ini

Artikel ini bukan teori akademis tentang tech debt. Ini adalah retrospektif nyata — momen ketika tim menyadari bahwa kecepatan pengembangan awal (MVP) kini menjadi utang teknis yang mencekik iterasi bisnis. Kita akan membedah biaya sesungguhnya, strategi migrasi, dan cara meyakinkan manajemen bahwa berhenti sejenak untuk memperbaiki fondasi adalah keputusan bisnis paling cerdas yang bisa diambil.


1. The Reckoning: Ketika MVP Menjadi Monster

1.1 Cerita yang Familiar

Setiap startup pernah di titik ini. Bulan pertama, semuanya terasa ajaib — fitur di-ship dalam hitungan hari, pivot terjadi dalam hitungan jam, dan "kita perbaiki nanti" menjadi mantra tim.

Kemudian "nanti" datang.

graph TD
    A["Bulan 1-3: MVP Phase<br/>🚀 Ship everything fast"] --> B["Bulan 4-6: Growth Phase<br/>📈 Fitur bertambah, tim berkembang"]
    B --> C["Bulan 7-12: Friction Phase<br/>⚠️ Bug meningkat, deploy melambat"]
    C --> D["Bulan 12-18: Crisis Phase<br/>🔥 Developer frustasi, bisnis terhambat"]
    D --> E{Pilihan}
    E -->|"Ignore"| F["💀 Technical Bankruptcy<br/>Rewrite dari nol"]
    E -->|"Act"| G["🔧 Strategic Refactoring<br/>Perbaiki sambil jalan"]

    style A fill:#d1fae5,stroke:#10b981,color:#000
    style B fill:#fef3c7,stroke:#f59e0b,color:#000
    style C fill:#fed7aa,stroke:#f97316,color:#000
    style D fill:#fee2e2,stroke:#ef4444,color:#000
    style F fill:#fecaca,stroke:#dc2626,color:#000
    style G fill:#bbf7d0,stroke:#22c55e,color:#000

1.2 Tanda-Tanda Tech Debt Sudah Compounding

The Silent Killer

Tech debt tidak pernah datang tiba-tiba. Ia compound — seperti bunga pinjaman yang tidak dibayar. Setiap sprint yang dihabiskan tanpa membayar utang, bunga bertambah. Dan suatu hari, bunganya lebih besar dari kemampuan tim untuk membayar.


2. The Cost of Bad Code: Menghitung Kerugian Bisnis

2.1 Dimana Waktu Developer Benar-Benar Habis

Salah satu momen paling menyadarkan adalah ketika kami melakukan time audit selama 4 sprint berturut-turut dan menemukan data ini:

pie title "Distribusi Waktu Developer — Codebase Sehat vs Bermasalah"
    "Feature Development" : 15
    "Merge Conflict Resolution" : 25
    "Bug Fixing (Regresi)" : 20
    "Workaround & Patching" : 15
    "Debugging Spaghetti Code" : 10
    "Code Review (Karena Complexity)" : 10
    "Waiting (Build/Deploy)" : 5
Data Point yang Mengejutkan

Developer menghabiskan 70% waktu hanya untuk mengatasi konflik kode, memperbaiki regresi, dan menavigasi arsitektur yang rapuh — alih-alih membangun fitur. Artinya dari 5 engineer, hanya 1.5 engineer yang benar-benar produktif membangun value baru.

2.2 Menghitung Kerugian dalam Rupiah

Mari kita kuantifikasi kerugian nyata ini agar bisa berbicara dalam bahasa yang dipahami manajemen.

analysis/healthy-team.ts
const healthyTeam = {
  engineers: 5,
  monthlyRate: 30_000_000, // per engineer, fully loaded
  totalMonthlyCost: 5 * 30_000_000, // = Rp 150,000,000

  timeDistribution: {
    featureDevelopment: 0.55, // 55%
    bugFix: 0.1, // 10%
    codeReview: 0.1, // 10%
    techDebt: 0.1, // 10% (proactive)
    meetings: 0.1, // 10%
    mergeConflicts: 0.05, // 5%
  },

  effectiveFeatureOutput: 5 * 0.55, // = 2.75 engineer equivalents
  monthlyFeatureValue: 2.75 * 30_000_000, // = Rp 82,500,000 worth of features
  velocityTrend: "accelerating",
};

2.3 The Compound Interest of Tech Debt

xychart-beta
    title "Proyeksi Velocity: Tanpa vs Dengan Investasi Refactoring"
    x-axis ["Q1", "Q2", "Q3", "Q4", "Q5", "Q6", "Q7", "Q8"]
    y-axis "Story Points per Sprint" 0 --> 80
    line "Tanpa Refactoring" [40, 35, 28, 22, 18, 14, 12, 10]
    line "Dengan Refactoring (20% sprint)" [40, 35, 32, 38, 45, 52, 58, 65]

Perhatikan grafik di atas. Kedua tim mulai dari titik yang sama (40 story points per sprint). Tim yang tidak berinvestasi di refactoring mengalami penurunan velocity yang terus-menerus — dari 40 ke 10 dalam 8 quarter.

Tim yang mengalokasikan 20% sprint untuk tech debt memang mengalami sedikit penurunan di awal (karena bandwidth berkurang), tetapi kemudian velocity-nya meledak karena codebase semakin bersih dan mudah di-extend.

The Math

Setelah 2 tahun, tim yang berinvestasi di refactoring menghasilkan output 6.5x lebih banyak dari tim yang mengabaikan tech debt — padahal mereka "hanya" menggunakan 80% waktu untuk fitur.


3. Migrasi Skala Besar: Zero Downtime Transformation

3.1 Anatomy of a Large-Scale Migration

Retrospektif berikut adalah dari proyek nyata: migrasi frontend legacy ke arsitektur modern tanpa mengganggu pelanggan aktif.

PropertyTypeDescription
Dari Legacy Frontend jQuery + server-rendered templates, monolithic CSS, tightly coupled dengan backend API, zero test coverage, 4 tahun akumulasi code tanpa standarisasi.
Ke Modern SPA/SSR React/Next.js (atau Vue/Nuxt, Flutter Web — tergantung konteks), component-based architecture, design system, API contract via OpenAPI, >80% test coverage.
Constraint Zero Downtime 50K+ pengguna aktif harian. Tidak boleh ada downtime atau degradasi UX selama proses migrasi. Bisnis tetap harus berjalan normal.
Timeline 6-9 bulan Paralel dengan feature development. Tim tidak bisa 'freeze' fitur baru selama migrasi karena bisnis tetap butuh iterasi.

3.2 The Strangler Fig Pattern

Strategi yang kami gunakan: Strangler Fig Pattern — secara bertahap mengganti komponen legacy dengan komponen baru, tanpa ever melakukan "big bang" switch.

graph TD
    subgraph "Phase 1: Foundation (Bulan 1-2)"
        A1["Setup project baru<br/>(React/Next.js)"] --> A2["Design System &<br/>Component Library"]
        A2 --> A3["API Gateway /<br/>BFF Layer"]
        A3 --> A4["Shared Auth &<br/>Session Management"]
    end

    subgraph "Phase 2: Migration (Bulan 3-6)"
        B1["Route-by-route migration<br/>Mulai dari halaman paling sederhana"] --> B2["Reverse proxy routing<br/>Legacy vs New"]
        B2 --> B3["Feature flag per route<br/>Gradual rollout 1% → 100%"]
        B3 --> B4["Parallel running &<br/>comparison testing"]
    end

    subgraph "Phase 3: Completion (Bulan 7-9)"
        C1["Migrate remaining<br/>complex pages"] --> C2["Performance optimization<br/>& monitoring"]
        C2 --> C3["Decommission legacy<br/>frontend"]
        C3 --> C4["Post-migration<br/>cleanup & documentation"]
    end

    A4 --> B1
    B4 --> C1

    style A1 fill:#dbeafe,stroke:#3b82f6,color:#000
    style A2 fill:#dbeafe,stroke:#3b82f6,color:#000
    style A3 fill:#dbeafe,stroke:#3b82f6,color:#000
    style A4 fill:#dbeafe,stroke:#3b82f6,color:#000
    style B1 fill:#fef3c7,stroke:#f59e0b,color:#000
    style B2 fill:#fef3c7,stroke:#f59e0b,color:#000
    style B3 fill:#fef3c7,stroke:#f59e0b,color:#000
    style B4 fill:#fef3c7,stroke:#f59e0b,color:#000
    style C1 fill:#d1fae5,stroke:#10b981,color:#000
    style C2 fill:#d1fae5,stroke:#10b981,color:#000
    style C3 fill:#d1fae5,stroke:#10b981,color:#000
    style C4 fill:#d1fae5,stroke:#10b981,color:#000

3.3 Reverse Proxy: Kunci Zero Downtime

Konsep paling critical: menggunakan reverse proxy (Nginx/Caddy/Traefik) untuk merutekan traffic antara legacy dan aplikasi baru berdasarkan path atau feature flag.

nginx/migration-routing.conf
# Phase 2: Route-based migration
# Halaman yang sudah dimigrasi → New App
# Halaman yang belum → Legacy App

upstream legacy_app {
    server 127.0.0.1:3000;
}

upstream new_app {
    server 127.0.0.1:4000;
}

server {
    listen 80;
    server_name app.example.com;

    # Halaman yang sudah dimigrasi ke new app
    location /dashboard {
        proxy_pass http://new_app;
    }

    location /settings {
        proxy_pass http://new_app;
    }

    location /reports {
        proxy_pass http://new_app;
    }

    # Semua halaman lain masih di legacy
    location / {
        proxy_pass http://legacy_app;
    }
}
Gradual Rollout

Dengan pendekatan ini, Anda bisa memigrasikan satu halaman per sprint, memvalidasi dengan pengguna nyata, dan rollback dalam hitungan detik jika ada masalah — cukup ubah konfigurasi proxy. Zero downtime, zero drama.

3.4 Lesson: Apa yang Berjalan Baik dan Apa yang Tidak

Design System First

Membangun component library dan design system sebelum memulai migrasi memastikan konsistensi UI dan mempercepat development halaman berikutnya secara eksponensial.

Feature Flag per Route

Menggunakan feature flag memungkinkan rollout bertahap (1% → 10% → 50% → 100%) dan rollback instan. Tidak ada satu pun insiden downtime selama migrasi.

Parallel Comparison Testing

Menjalankan kedua versi secara paralel dan membandingkan output membantu menangkap perbedaan perilaku sebelum rollout penuh.

API Contract via OpenAPI

Mendefinisikan kontrak API sebelum migrasi memastikan backend dan frontend bisa berkembang secara independen.


4. Menjual Refactor ke Manajemen

4.1 Mengapa Ini Sulit

graph LR
    subgraph "Perspektif Manajemen"
        M1["Refactoring = <br/>Tidak ada fitur baru"] --> M2["Tidak ada fitur baru = <br/>Tidak ada growth"]
        M2 --> M3["Tidak ada growth = <br/>Investor kecewa"]
    end

    subgraph "Perspektif Engineering"
        E1["Tanpa refactoring = <br/>Velocity terus turun"] --> E2["Velocity turun = <br/>Fitur makin lambat"]
        E2 --> E3["Fitur makin lambat = <br/>Kompetitor menang"]
    end

    M3 ---|"Ketegangan Abadi"| E3

    style M3 fill:#fee2e2,stroke:#ef4444,color:#000
    style E3 fill:#fee2e2,stroke:#ef4444,color:#000
The Communication Gap

Ketika engineer bilang "kita butuh refactoring", manajemen mendengar "kita mau main-main dengan kode tanpa menghasilkan sesuatu yang terlihat oleh customer." Gap komunikasi ini adalah akar masalah. Solusinya bukan menghindari percakapan — tapi mengubah bahasanya.

4.2 Framework: The Refactoring Business Case

Jangan pernah mendatangi manajemen dengan "kita perlu refactor karena kodenya jelek." Datang dengan business case yang terstruktur.

Quantify the Problem (Data, Bukan Opini)

Kumpulkan data selama 2-4 sprint:

business-case/problem-quantification.ts
const currentState = {
  // Velocity metrics
  avgVelocity: {
    sixMonthsAgo: 42, // story points per sprint
    current: 18, // story points per sprint
    trend: "declining",
    projectedIn6Months: 8,
  },

  // Time distribution
  timeOnFeatures: 0.15, // 15% — should be 50%+
  timeOnBugFixes: 0.25, // 25% — should be <15%
  timeOnMergeConflicts: 0.2, // 20% — should be <5%

  // Business impact
  avgFeatureDeliveryTime: {
    sixMonthsAgo: "5 hari",
    current: "18 hari",
  },

  // Incident frequency
  productionIncidents: {
    sixMonthsAgo: "1 per bulan",
    current: "4 per bulan",
  },

  // Team health
  developerSatisfaction: 3.2, // out of 10
  attritionRisk: "HIGH",
};

Calculate the Cost of Inaction

business-case/cost-of-inaction.ts
const costOfInaction = {
  // Wasted engineer time per bulan
  wastedTimePerMonth: {
    engineers: 5,
    wastedPercentage: 0.55, // 55% waktu terbuang
    monthlyCost: 5 * 30_000_000 * 0.55,
    // = Rp 82,500,000 per bulan terbuang
  },

  // Projected over 12 months
  annualWaste: 82_500_000 * 12,
  // = Rp 990,000,000 (hampir Rp 1 Miliar!)

  // Attrition cost jika developer resign
  attritionCost: {
    probabilityOfResignation: 0.4, // 40% risk
    costPerReplacement: 150_000_000, // 5x monthly salary
    expectedAttritionCost: 0.4 * 2 * 150_000_000,
    // = Rp 120,000,000 (2 engineers likely to leave)
  },

  // Opportunity cost: fitur yang tidak di-ship
  lostRevenue: {
    featuresDelayed: 8, // per quarter
    avgRevenuePerFeature: 50_000_000,
    quarterlyLoss: 8 * 50_000_000,
    // = Rp 400,000,000 per quarter
  },

  totalAnnualCost: 990_000_000 + 120_000_000 + 400_000_000 * 4,
  // = Rp 2,710,000,000 (Rp 2.71 Miliar!)
};

Present the Investment (Bukan "Biaya")

business-case/refactoring-investment.ts
const refactoringInvestment = {
  duration: "8 minggu focused refactoring",
  teamAllocation: "3 dari 5 engineers (2 tetap di fitur)",
  investmentCost: 3 * 30_000_000 * 2, // 3 eng x 2 bulan
  // = Rp 180,000,000

  expectedOutcomes: {
    velocityRecovery: "18 → 35 story points (+94%)",
    featureDeliveryTime: "18 hari → 7 hari (-61%)",
    bugRate: "-60% production incidents",
    mergeConflicts: "-80% conflict resolution time",
    developerSatisfaction: "3.2 → 7.5 (out of 10)",
  },

  roi: {
    annualSavings: 990_000_000 * 0.6, // 60% waste recovered
    // = Rp 594,000,000 per tahun
    investmentCost: 180_000_000,
    roiMultiple: 594_000_000 / 180_000_000,
    // = 3.3x ROI in year 1
    paybackPeriod: "~4 bulan",
  },
};

Show the Timeline with Milestones

%%MDX_BLK_0_5%%

Propose Risk Mitigation

4.3 Pitch Deck: Satu Slide yang Meyakinkan

Jika Anda hanya punya satu slide untuk meyakinkan manajemen, gunakan ini:

Tanpa Refactoring Dengan Refactoring
Investasi Rp 0 Rp 180 Juta (8 minggu)
Kerugian Tahunan Rp 2.71 Miliar Rp 990 Juta
Feature Delivery 18 hari/fitur (memburuk) 7 hari/fitur (membaik)
Production Incidents 4x/bulan (naik) 1x/bulan (stabil)
Developer Attrition Risk 40% (2 orang) 10% (normal)
Velocity Trend Turun 50% per 6 bulan Naik 20% per quarter
Net Impact (Year 1) -Rp 2.71 Miliar +Rp 594 Juta savings
ROI N/A 3.3x
Kalimat Penutup Pitch

"Menghentikan rilis fitur selama 8 minggu untuk memperbaiki fondasi bukan menghambat bisnis — ini menyelamatkan Rp 1.72 Miliar per tahun dan mengembalikan kemampuan tim untuk bergerak cepat. Tanpa investasi ini, dalam 12 bulan kita akan membutuhkan rewrite total yang biayanya 5-10x lebih besar."


5. Playbook: Strategi Refactoring yang Terukur

5.1 Prioritization Matrix

Tidak semua tech debt sama. Gunakan matrix ini untuk memprioritaskan:

quadrantChart
    title Tech Debt Prioritization Matrix
    x-axis "Low Effort to Fix" --> "High Effort to Fix"
    y-axis "Low Business Impact" --> "High Business Impact"
    quadrant-1 "Plan & Schedule"
    quadrant-2 "Quick Wins — Do First!"
    quadrant-3 "Ignore / Accept"
    quadrant-4 "Strategic Project"
    "Merge conflicts": [0.3, 0.9]
    "Slow CI/CD": [0.2, 0.7]
    "No tests on Module A": [0.4, 0.8]
    "Legacy CSS": [0.7, 0.3]
    "Old logging format": [0.2, 0.2]
    "Database migration": [0.8, 0.85]
    "Monolith decomposition": [0.9, 0.7]
    "Outdated docs": [0.3, 0.15]

5.2 The 20% Rule

Sustainable Debt Management

Setelah refactoring besar selesai, alokasikan 20% setiap sprint untuk tech debt prevention. Ini seperti membayar cicilan — kecil, konsisten, dan mencegah utang menumpuk lagi. 20% hari ini mencegah 100% rewrite besok.

strategy/sprint-allocation.ts
interface SprintAllocation {
  featureDevelopment: number;
  techDebtPaydown: number;
  bugFixes: number;
}

const sustainableAllocation: SprintAllocation = {
  featureDevelopment: 70, // Fitur baru untuk bisnis
  techDebtPaydown: 20, // Bayar cicilan tech debt
  bugFixes: 10, // Buffer untuk bug tak terduga
};

// Monitor: Jika bugFixes konsisten > 20%,
// itu tanda tech debt masih terlalu tinggi

5.3 Metrics Dashboard: Track the Recovery

Setelah refactoring dimulai, track metrik ini setiap sprint:

PropertyTypeDescription
Velocity Trend Story Points per Sprint Target: kembali ke baseline dalam 3-4 sprint setelah refactoring. Jika belum naik setelah 4 sprint, evaluasi ulang scope refactoring.
Cycle Time Jam dari commit pertama hingga production Target: <2 hari. Metrik ini menunjukkan seberapa lancar pipeline development. Penurunan cycle time adalah indikator paling awal bahwa refactoring berhasil.
Merge Conflict Rate Jumlah conflict per PR Target: <1 conflict per 5 PR. Jika masih tinggi, fokus pada modularisasi dan domain boundary yang lebih jelas.
Bug Escape Rate Bugs yang lolos ke production per sprint Target: <2 per sprint. Penurunan metrik ini menunjukkan bahwa test coverage dan code quality membaik.
Developer Satisfaction Survey score (1-10) Target: >7. Metrik yang sering diabaikan tapi paling prediktif terhadap attrition. Survey anonim setiap 2 minggu.

6. Lessons Learned: What We'd Do Differently

Jangan Tunggu Sampai Crisis

Kami mulai refactoring ketika velocity sudah turun 57%. Seharusnya dimulai ketika tanda pertama muncul — saat velocity mulai plateau atau merge conflict mulai meningkat. Semakin dini dimulai, semakin murah biayanya. Rule of thumb: jika velocity turun 15% selama 2 sprint berturut-turut, itu sudah alarm.

Refactoring Butuh Ownership, Bukan Committee

Kami awalnya membentuk "tech debt committee" yang meeting setiap minggu untuk memutuskan apa yang di-refactor. Hasilnya: banyak meeting, sedikit progress. Yang berhasil: menunjuk 1 senior engineer sebagai "Refactoring Lead" dengan otoritas untuk membuat keputusan harian. Committee hanya mereview di akhir sprint.

Measure Before, During, and After

Tanpa baseline metrics, Anda tidak bisa membuktikan bahwa refactoring berhasil. Kami hampir gagal menjustifikasi investasi karena tidak punya data "sebelum" yang solid. Selalu kumpulkan 2-4 sprint data sebelum memulai refactoring.

Komunikasi Berlebih Lebih Baik dari Kurang

Setiap Jumat, kami mengirim update ke Slack channel yang diikuti manajemen: apa yang diperbaiki, metrik yang berubah, dan screenshot before/after. Ini membangun trust dan mencegah pertanyaan "kapan refactoring selesai?" yang bisa merusak moral tim.

Jangan Refactor Semua Sekaligus

Godaan terbesar: "sekalian saja kita perbaiki semuanya." Ini resep disaster. Fokus pada 2-3 area dengan impact tertinggi dulu. Hasilnya akan memberikan momentum dan bukti untuk refactoring area berikutnya.


7. Conclusion

TL;DR

Tech debt bukan masalah teknis — ini masalah bisnis yang kebetulan manifest di kode. Cara mengatasinya bukan dengan bahasa teknis, tapi dengan bahasa bisnis: kerugian yang terukur, investasi yang terkuantifikasi, dan ROI yang bisa dipertanggungjawabkan.

Akui Bahwa Kecepatan MVP Punya Harga

Setiap shortcut di masa lalu adalah pinjaman yang kini harus dibayar. Bukan kesalahan — itu keputusan yang tepat saat itu. Tapi sekarang konteksnya berbeda, dan hutangnya harus dikelola.

Kuantifikasi Kerugian, Bukan Keluhan

Jangan bilang "kodenya berantakan." Bilang: "Tim kehilangan Rp 720 Juta per tahun karena 70% waktu terbuang untuk navigasi arsitektur yang rapuh." Data mengalahkan opini.

Migrasi Bertahap, Bukan Big Bang

Strangler Fig Pattern. Feature flags. Reverse proxy. Zero downtime. Tidak ada alasan untuk mempertaruhkan bisnis yang sedang berjalan demi refactoring.

Jual dengan ROI, Bukan Ego

Manajemen tidak peduli bahwa kode Anda "lebih bersih." Mereka peduli bahwa investasi Rp 180 Juta menghasilkan penghematan Rp 594 Juta per tahun. Frame as investment, not expense.

Cegah, Jangan Tunggu Krisis

Alokasikan 20% setiap sprint untuk tech debt. Bayar cicilan. Jangan tunggu sampai bunganya lebih besar dari principal.

"The best time to refactor was 6 months ago. The second best time is now." Setiap hari yang berlalu tanpa tindakan, bunga tech debt bertambah. Mulai hari ini — bukan dengan rewrite besar-besaran, tapi dengan satu langkah terukur yang membuktikan bahwa memperbaiki fondasi adalah investasi, bukan biaya.