Bot oceniający złożoność pull requestów przez webhooki GitHub

7 lutego 2025

Pull request może zmieniać jedną etykietę albo przebudowywać kilka kluczowych modułów. Taki sam status „gotowy do przeglądu” niewiele mówi osobie planującej pracę zespołu. W tym poradniku zbudujemy prostą ocenę złożoności, która pomaga oszacować wysiłek potrzebny na review.

Wynik jest heurystyką, a nie oceną jakości kodu. Nie powinien blokować merge’a ani zastępować decyzji inżyniera.

Jakie sygnały wykorzystamy

Bot uwzględni:

  • liczbę zmienionych plików,
  • liczbę dodanych i usuniętych linii,
  • przybliżoną złożoność rozgałęzień w zmienionym kodzie.

Każdy sygnał ma ograniczony wpływ na wynik. Dzięki temu jeden bardzo duży plik nie dominuje całej oceny.

Złożoność cyklomatyczna

Złożoność cyklomatyczna opisuje liczbę niezależnych ścieżek wykonania. Więcej instrukcji warunkowych i pętli zwykle oznacza więcej przypadków do zrozumienia i przetestowania.

Przykład z głębokim zagnieżdżeniem:

1function canAccessFeature(user) {
2 if (!user) return false;
3
4 if (user.role === 'admin') {
5 return true;
6 }
7
8 if (user.subscription) {
9 if (user.subscription.active) {
10 return user.permissions.includes('feature_access');
11 }
12 }
13
14 return false;
15}

Ta sama logika jest łatwiejsza do przejrzenia po zastosowaniu wczesnych zwrotów:

1function canAccessFeature(user) {
2 if (!user) return false;
3 if (user.role === 'admin') return true;
4 if (!user.subscription?.active) return false;
5 return user.permissions.includes('feature_access');
6}

Sama liczba rozgałęzień nie zawsze spadnie, ale struktura kodu stanie się czytelniejsza. To dobry przykład ograniczenia pojedynczej metryki.

Obliczanie wyniku

1function computePRComplexityScore({
2 filesChanged,
3 linesAdded,
4 linesRemoved,
5 cyclomaticComplexity,
6}) {
7 const fileScore = Math.min(filesChanged / 10, 1) * 2;
8 const lineScore = Math.min((linesAdded + linesRemoved) / 500, 1) * 1;
9 const branchScore = Math.min(cyclomaticComplexity / 10, 1) * 3;
10
11 const weighted = (fileScore + lineScore + branchScore) / 6;
12 return (weighted * 10).toFixed(1);
13}

Wagi są decyzją produktową, nie prawem matematycznym. Powinny zostać skalibrowane na podstawie rzeczywistych pull requestów danego zespołu.

Webhook GitHub

Po odebraniu zdarzenia pobieramy statystyki i zmienione pliki przez Octokit. Następnie obliczamy wynik i publikujemy komentarz.

1const { data: pullRequest } = await octokit.pulls.get({
2 owner,
3 repo,
4 pull_number: pullNumber,
5});
6
7const { data: files } = await octokit.pulls.listFiles({
8 owner,
9 repo,
10 pull_number: pullNumber,
11});
12
13const score = computePRComplexityScore({
14 filesChanged: pullRequest.changed_files,
15 linesAdded: pullRequest.additions,
16 linesRemoved: pullRequest.deletions,
17 cyclomaticComplexity: estimateComplexity(files),
18});

Proste wyrażenie regularne może wystarczyć do demonstracji, ale nie jest parserem kodu. W produkcji lepiej użyć narzędzia rozumiejącego składnię konkretnego języka.

Jak używać wyniku

Ocena może sugerować, że pull request warto podzielić, przypisać do niego dwóch reviewerów albo zarezerwować więcej czasu. Nie powinna mówić, że kod jest dobry lub zły. Najlepszy komentarz pokazuje nie tylko liczbę, ale również jej składniki i ograniczenia.

Po wdrożeniu warto porównywać wynik z rzeczywistym czasem review. Jeśli korelacja jest słaba, zmień wagi albo zrezygnuj z metryki. Automatyzacja ma pomagać zespołowi podejmować decyzje, nie produkować kolejną liczbę bez znaczenia.

Michał Winiarski

Michał Winiarski

Founder of Devbrains and senior software developer

Najnowsze artykuły