Membangun model yang mampu memprediksi modul mana yang paling berisiko memiliki bug โ sebelum pengujian dilakukan. Fokuskan energi QA di tempat yang paling dibutuhkan.
Tanpa prediksi defect: Tim QA menguji semua modul dengan intensitas yang sama โ seperti dokter umum yang memeriksa setiap pasien dari ujung kepala hingga ujung kaki tanpa mempertimbangkan risiko.
Dengan prediksi defect: Model ML mengidentifikasi modul mana yang paling "berisiko" berdasarkan karakteristik kodenya. Tim QA bisa berlaku seperti dokter spesialis โ memberikan perhatian ekstra pada modul berisiko tinggi, dan pengujian ringan pada modul stabil.
Hasilnya: Dengan waktu dan sumber daya yang sama, tim QA menemukan jauh lebih banyak bug yang signifikan.
NASA mengumpulkan data dari proyek-proyek software luar angkasa mereka selama puluhan tahun โ setiap modul kode dicatat metriknya (kompleksitas, ukuran, riwayat perubahan) beserta apakah akhirnya ditemukan bug atau tidak. Data ini menjadi "dataset emas" untuk melatih model prediksi defect. Seperti belajar dari rekaman penerbangan pesawat untuk memprediksi mana yang akan bermasalah.
Seperti kartu rapor siswa yang mencatat nilai matematika, bahasa, olahraga, kehadiran, dll โ model ML membutuhkan "kartu rapor" numerik untuk setiap modul kode. Feature engineering adalah proses membuat kartu rapor ini dari kode sumber. Semakin relevan fiturnya, semakin akurat prediksi "siswa mana yang mungkin tidak lulus ujian (punya bug)".
| Kategori | Fitur | Cara Ekstraksi | Relevansi |
|---|---|---|---|
| Metrik Kode Statis Static |
Cyclomatic Complexity (CC), Lines of Code (LOC), Number of Methods, Depth of Inheritance | Tools: pylint, radon (Python), SonarQube, Understand | CC tinggi โ banyak cabang โ lebih susah ditest โ lebih rawan bug |
| Metrik Halstead Static |
Program Vocabulary, Volume, Difficulty, Effort | Dihitung dari jumlah operator/operand unik dalam kode | Halstead Difficulty tinggi โ kode sulit dipahami โ lebih rawan kesalahan |
| Metrik Proses Process |
Number of commits, Number of authors, Age of code, Lines changed per commit | Git history: git log, git blame, git shortstat | Banyak author + frequent change โ higher defect density |
| Metrik Riwayat History |
Bug count sebelumnya, Reopened bug count, Fix time average | Jira/GitHub Issues API, bug tracker export | "Bug masa lalu adalah prediktor terbaik bug masa depan" |
| Object-Oriented Static |
CK Metrics: WMC, DIT, NOC, CBO, RFC, LCOM | Tools: ckjm (Java), pymetrics (Python) | CBO (Coupling) tinggi โ banyak dependensi โ bug di satu class merembet |
Aturan emas feature engineering: "Process metrics" (riwayat commit, jumlah author) biasanya lebih prediktif daripada "code metrics" semata. Kombinasi keduanya memberikan hasil terbaik.
Bayangkan test COVID dengan akurasi 99% โ kedengarannya hebat! Tapi jika prevalensi COVID di populasi hanya 1%, model yang selalu bilang "negatif" sudah mencapai akurasi 99%. Model tersebut TIDAK berguna sama sekali. Untuk prediksi defect, yang penting bukan akurasi global, tapi seberapa baik model menemukan modul yang benar-benar buggy (recall/sensitivity tinggi pada kelas defective).
| Pred: CLEAN | Pred: BUGGY | |
|---|---|---|
| Actual: CLEAN | 142 True Negative (TN) |
18 False Positive (FP) Over-testing |
| Actual: BUGGY | 8 False Negative (FN) โ Berbahaya! |
32 True Positive (TP) Bug terdeteksi โ |
Untuk prediksi defect: Recall (sensitivity) adalah metrik terpenting. False Negative (bug yang tidak terdeteksi dan lolos ke production) jauh lebih mahal dari False Positive (modul clean yang dicurigai buggy).
Bayangkan mengajarkan model untuk mengenali huruf "Z" dari dataset 10.000 huruf, tapi "Z" hanya muncul 200 kali (2%). Model cenderung "malas" โ lebih mudah bilang bukan Z untuk semua huruf dan mencapai akurasi 98%. Ini analog dengan dataset defect: biasanya hanya 15โ30% modul yang buggy. Model naif akan prediksi "clean" semua dan tetap akurat tinggi.
SMOTE (Synthetic Minority Over-sampling Technique) menciptakan sampel sintetis baru dari kelas minoritas (buggy) dengan cara interpolasi antara sampel yang ada โ bukan sekadar menduplikasi, tapi membuat variasi baru yang "realistis".
*600 sampel sintetis dibuat SMOTE dari 100 sampel asli buggy.
Penting: SMOTE hanya diterapkan pada data training, bukan data test! Data test harus tetap merepresentasikan distribusi data nyata.
Ambil dataset (NASA MDP / PROMISE / historis sendiri). Tangani missing values, hapus duplikat, normalisasi format. Verifikasi label akurasi.
Distribusi kelas, korelasi fitur, outlier detection. Pahami karakteristik dataset sebelum modelling. Plot pairplot, heatmap korelasi.
Hapus fitur dengan korelasi sangat tinggi (multicollinearity). Buat fitur baru jika relevan (rasio, interaksi). Normalisasi jika diperlukan.
Split 80% train / 20% test dengan stratified sampling. Terapkan SMOTE HANYA pada data training. Test set tetap asli.
Latih multiple model (Logistic Regression, Random Forest, XGBoost). Gunakan GridSearchCV atau RandomizedSearchCV untuk tuning. Cross-validation 5-fold.
Confusion matrix, Precision, Recall, F1, AUC-ROC. Feature importance dari Random Forest/XGBoost. SHAP values untuk explainability.
Integrasikan ke CI/CD pipeline. Saat ada code push, model otomatis menilai risiko setiap modul yang berubah. Hasilkan laporan risiko untuk QA team.
# ================================================================ # S11409 - Sesi 9: Software Defect Prediction (SDP) Pipeline # Dataset: NASA KC1 (simulasi dengan fitur realistis) # Dosen: Riadi Marta Dinata, S.Ti., M.Kom. | ISTN Jakarta # ================================================================ # pip install scikit-learn imbalanced-learn xgboost pandas numpy # matplotlib seaborn shap import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns import warnings; warnings.filterwarnings('ignore') from sklearn.model_selection import train_test_split, cross_val_score, StratifiedKFold from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LogisticRegression from sklearn.tree import DecisionTreeClassifier from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import (classification_report, confusion_matrix, roc_auc_score, RocCurveDisplay) from imblearn.over_sampling import SMOTE from imblearn.pipeline import Pipeline as ImbPipeline import xgboost as xgb # โโ STEP 1: BUAT DATASET SIMULASI NASA KC1-STYLE โโ np.random.seed(42) n_samples = 1000 # Fitur kode (terinspirasi NASA KC1) df = pd.DataFrame({ # Halstead Metrics 'halstead_volume': np.random.lognormal(5, 1.5, n_samples), 'halstead_effort': np.random.lognormal(8, 2.0, n_samples), 'halstead_difficulty': np.random.lognormal(2, 1.0, n_samples), # McCabe / Cyclomatic Complexity 'cyclomatic_complexity': np.random.lognormal(1.5, 0.8, n_samples).astype(int) + 1, 'essential_complexity': np.random.lognormal(1.2, 0.7, n_samples).astype(int) + 1, # Size Metrics 'loc': np.random.lognormal(4, 1.2, n_samples).astype(int), 'num_operands': np.random.lognormal(4, 1.0, n_samples).astype(int), 'num_operators': np.random.lognormal(3.5, 1.0, n_samples).astype(int), # Process Metrics 'num_commits': np.random.poisson(8, n_samples), 'num_authors': np.random.randint(1, 8, n_samples), 'code_age_months': np.random.randint(1, 36, n_samples), 'prev_bugs': np.random.poisson(1.5, n_samples), }) # Label: buggy (1) jika kombinasi fitur berisiko tinggi # (simulasi hubungan yang realistis) risk_score = ( 0.3 * (df['cyclomatic_complexity'] > 10).astype(float) + 0.25 * (df['num_authors'] > 4).astype(float) + 0.2 * (df['prev_bugs'] > 0).astype(float) + 0.15 * (df['loc'] > 200).astype(float) + 0.1 * (df['num_commits'] > 12).astype(float) + np.random.normal(0, 0.15, n_samples) # noise ) df['buggy'] = (risk_score > 0.5).astype(int) X = df.drop('buggy', axis=1) y = df['buggy'] print("="*60) print(" SOFTWARE DEFECT PREDICTION PIPELINE") print(" NASA KC1 Style Dataset | S11409 ISTN") print("="*60) print(f"\nDataset: {len(df)} modul kode") print(f"Clean : {(y==0).sum()} ({(y==0).mean():.1%})") print(f"Buggy : {(y==1).sum()} ({(y==1).mean():.1%})") print(f"Fitur : {X.shape[1]} fitur") # โโ STEP 2: SPLIT DATA โโ X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.20, random_state=42, stratify=y) print(f"\nTraining: {len(X_train)} | Test: {len(X_test)}") # โโ STEP 3: DEFINISI MODELS โโ models = { 'Logistic Regression': ImbPipeline([ ('smote', SMOTE(random_state=42)), ('scaler', StandardScaler()), ('clf', LogisticRegression(max_iter=1000, random_state=42)) ]), 'Random Forest': ImbPipeline([ ('smote', SMOTE(random_state=42)), ('clf', RandomForestClassifier( n_estimators=200, max_depth=10, class_weight='balanced', random_state=42)) ]), 'XGBoost': ImbPipeline([ ('smote', SMOTE(random_state=42)), ('clf', xgb.XGBClassifier( n_estimators=200, learning_rate=0.05, max_depth=5, scale_pos_weight=3, use_label_encoder=False, eval_metric='logloss', random_state=42)) ]), } # โโ STEP 4: TRAIN & EVALUATE โโ results = {} cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) print("\n{'Model':<22} {'AUC-ROC':>10} {'F1':>8} {'Recall':>8} {'Precision':>12}") print("-"*65) for name, pipeline in models.items(): pipeline.fit(X_train, y_train) y_pred = pipeline.predict(X_test) y_prob = pipeline.predict_proba(X_test)[:, 1] from sklearn.metrics import f1_score, recall_score, precision_score auc = roc_auc_score(y_test, y_prob) f1 = f1_score(y_test, y_pred) rec = recall_score(y_test, y_pred) prec = precision_score(y_test, y_pred) results[name] = {'auc': auc, 'f1': f1, 'recall': rec, 'precision': prec, 'model': pipeline, 'y_pred': y_pred} print(f"{name:<22} {auc:>10.3f} {f1:>8.3f} {rec:>8.3f} {prec:>12.3f}") # โโ STEP 5: FEATURE IMPORTANCE (Random Forest) โโ rf_model = results['Random Forest']['model'].named_steps['clf'] feat_imp = pd.Series(rf_model.feature_importances_, index=X.columns) feat_imp = feat_imp.sort_values(ascending=False).head(8) print("\n--- Top 8 Feature Importance (Random Forest) ---") for feat, imp in feat_imp.items(): bar = 'โ' * int(imp * 100) print(f" {feat:<25} {bar} {imp:.3f}") # โโ STEP 6: VISUALISASI โโ fig, axes = plt.subplots(1, 3, figsize=(16, 5)) fig.patch.set_facecolor('#0A0608') # Plot 1: Confusion Matrix terbaik (XGBoost) ax1 = axes[0] ax1.set_facecolor('#160C0E') cm = confusion_matrix(y_test, results['XGBoost']['y_pred']) sns.heatmap(cm, annot=True, fmt='d', ax=ax1, cmap='RdYlGn', linewidths=0.5, xticklabels=['Clean','Buggy'], yticklabels=['Clean','Buggy']) ax1.set_title('Confusion Matrix โ XGBoost', color='white', pad=10) ax1.tick_params(colors='#9B7A80') # Plot 2: Feature Importance ax2 = axes[1] ax2.set_facecolor('#160C0E') feat_imp.plot(kind='barh', ax=ax2, color=['#C8102E','#E8374F','#F97316','#FCD34D', '#22C55E','#38BDF8','#A78BFA','#9B7A80']) ax2.set_title('Feature Importance (Random Forest)', color='white', pad=10) ax2.tick_params(colors='#9B7A80') ax2.invert_yaxis() # Plot 3: ROC Curve perbandingan ax3 = axes[2] ax3.set_facecolor('#160C0E') colors_roc = {'Logistic Regression': '#38BDF8', 'Random Forest': '#F97316', 'XGBoost': '#C8102E'} for name, res in results.items(): y_prob = res['model'].predict_proba(X_test)[:, 1] RocCurveDisplay.from_predictions( y_test, y_prob, name=f"{name} (AUC={res['auc']:.3f})", ax=ax3, color=colors_roc[name]) ax3.plot([0,1],[0,1], '--', color='#4B5563', label='Random') ax3.set_title('ROC Curve โ Model Comparison', color='white', pad=10) ax3.tick_params(colors='#9B7A80') ax3.legend(fontsize=9) ax3.grid(alpha=0.15) plt.tight_layout() plt.savefig('sdp_results.png', dpi=150, facecolor='#0A0608', bbox_inches='tight') print("\nโ Hasil tersimpan: sdp_results.png") # โโ STEP 7: PREDIKSI MODUL BARU โโ print("\n--- Prediksi Risiko Modul Kode Baru ---") modul_baru = pd.DataFrame([ {'halstead_volume':1200, 'halstead_effort':50000, 'halstead_difficulty':25, 'cyclomatic_complexity':18, 'essential_complexity':12, 'loc':320, 'num_operands':180, 'num_operators':120, 'num_commits':15, 'num_authors':6, 'code_age_months':18, 'prev_bugs':3}, {'halstead_volume':80, 'halstead_effort':1200, 'halstead_difficulty':3, 'cyclomatic_complexity':3, 'essential_complexity':2, 'loc':35, 'num_operands':20, 'num_operators':15, 'num_commits':3, 'num_authors':1, 'code_age_months':2, 'prev_bugs':0}, ]) modul_names = ["PaymentProcessor.py", "StringUtils.py"] best_model = results['XGBoost']['model'] proba_baru = best_model.predict_proba(modul_baru)[:, 1] for nama, prob in zip(modul_names, proba_baru): risk = "๐ด HIGH RISK" if prob > 0.6 else ("๐ก MEDIUM" if prob > 0.35 else "๐ข LOW RISK") print(f" {nama:<30} Risk: {prob:.1%} {risk}")
Anda memiliki akses ke repository Git proyek SIAKAD ISTN. Sebutkan dan jelaskan minimal 6 fitur yang bisa Anda ekstrak dari Git history (bukan dari kode sumber) untuk prediksi defect. Mengapa fitur-fitur ini relevan?
Model A: Accuracy 92%, Recall 45%. Model B: Accuracy 78%, Recall 85%. Untuk digunakan dalam prediksi defect production system e-banking, mana yang lebih baik? Jelaskan dengan analogi biaya False Negative vs False Positive.
Dataset Anda memiliki 900 modul clean dan 100 modul buggy. (a) Apa risiko melatih model tanpa penanganan imbalanced? (b) Jelaskan cara kerja SMOTE dan berapa sampel sintetis yang dibuat untuk mencapai rasio 1:1. (c) Mengapa SMOTE tidak boleh diterapkan pada data test?
Feature importance model Anda menunjukkan: prev_bugs (0.31), cyclomatic_complexity (0.22), num_authors (0.18), loc (0.12). Apa 3 rekomendasi konkret yang Anda berikan kepada tim developer berdasarkan data ini?
Modifikasi kode di atas untuk: (a) Tambahkan Decision Tree ke daftar model yang dibandingkan, (b) Implementasikan threshold tuning โ ubah threshold dari default 0.5 menjadi 0.35 untuk meningkatkan recall, (c) Hitung berapa penghematan (dalam jam testing) jika model memprioritaskan 30% modul tertinggi risikonya vs menguji semua modul secara merata.
Prediksi defect bukan memprediksi dengan sempurna โ tapi memprioritaskan modul berisiko tinggi agar QA effort lebih efisien
Process metrics (git history, num authors) seringkali lebih prediktif dari code metrics murni โ kombinasi keduanya terbaik
Recall adalah raja untuk defect prediction โ False Negative (bug lolos) jauh lebih mahal dari False Positive
SMOTE mengatasi imbalanced data dengan membuat sampel sintetis kelas minoritas โ hanya di training set!
XGBoost umumnya terbaik secara akurasi; Random Forest lebih mudah diinterpretasi lewat feature importance
AUC-ROC > 0.8 adalah target yang baik; di bawah 0.7 perlu perbaikan feature engineering atau lebih banyak data