Умеешь ли искать ошибки в программе
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Debugging: искусство находить ошибки
Да, это один из моих ключевых навыков после 10 лет опыта.
Методология Debug-а
1. Понять проблему
- Что сломалось?
- Когда началось?
- Как воспроизвести?
2. Выдвинуть гипотезы
- Это может быть A, B или C
- Какая вероятность каждого?
3. Тестировать гипотезы
- Проверить логи
- Запустить в debugger
- Добавить временный logging
4. Найти root cause
- Это не症状, это причина
5. Fix и verify
- Исправить
- Убедиться, что работает
- Убедиться, что не сломал ничего другого
Инструменты, которые я использую
1. Логирование (первое, что проверяю)
logger.error("Payment failed", exception);
// Логи показывают стек трейс
awk 'grep error /var/log/app.log | tail -100'
// 100 последних ошибок
2. IDE Debugger (IntelliJ IDEA)
- Breakpoint на строку кода
- Пошаговое выполнение (Step Into, Step Over)
- Inspect variables
- Evaluate expressions
- Watch expressions
3. Database queries
SELECT * FROM transactions WHERE user_id = 123 ORDER BY created_at DESC LIMIT 10;
-- Проверяю: правильно ли данные в БД?
4. Network requests (curl, Postman)
curl -v http://api/users/123
// -v показывает headers, status, response body
5. System monitoring
jps -l // список Java процессов jstack <pid> // все потоки и их stack traces jmap -heap <pid> // memory usage
6. Production tools
- Datadog / New Relic (performance monitoring)
- ELK stack (логи)
- Prometheus + Grafana (метрики)
Пример 1: API возвращает 500 ошибку
Шаг 1: Логи
ERROR [2024-03-20 14:32:10] NullPointerException at PaymentService:145
at com.example.payment.PaymentService.processPayment(...)
at com.example.controller.PaymentController.pay(...)
Шаг 2: Посмотри код на строке 145
BigDecimal amount = order.getDiscount(); // NPE здесь?
amount = amount.add(tax); // Если amount = null
Шаг 3: Проверь данные в БД
SELECT * FROM orders WHERE id = 12345;
-- discount column NULL в некоторых заказах
Шаг 4: Fix
BigDecimal discount = order.getDiscount() != null ?
order.getDiscount() : BigDecimal.ZERO;
BigDecimal amount = discount.add(tax);
Шаг 5: Verify
grep "NullPointerException" /var/log/app.log | wc -l
# Было: 150 ошибок в день
# Теперь: 0
Пример 2: Запрос выполняется 30 секунд
Шаг 1: Профилируй
API /api/users/search
Total time: 30 000ms
Database query: 29 500ms (98%)
JSON serialization: 400ms
Other: 100ms
Проблема в БД!
Шаг 2: EXPLAIN ANALYZE
EXPLAIN ANALYZE
SELECT * FROM users WHERE email LIKE 'john%' OR phone LIKE 'john%';
-- Результат: Seq Scan, 500M rows
-- Нет индекса!
Шаг 3: Fix
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_phone ON users(phone);
Шаг 4: Verify
API /api/users/search: 30 000ms → 150ms (200x быстрее)
Пример 3: Memory утечка (heap grows)
Шаг 1: Мониторинг
Day 1: Heap 500MB
Day 2: Heap 600MB
Day 3: Heap 700MB
Day 7: Heap 3000MB → OutOfMemoryError
Шаг 2: Dump heap
jmap -dump:live,format=b,file=heap.bin <pid>
jhat -J-Xmx4g heap.bin
Шаг 3: Analyze
Top objects:
1. String[]: 1000000 instances, 1.5GB memory
2. All same string: "cached_item"
Шаг 4: Find in code
private static List<String> cache = new ArrayList<>();
public void addToCache(String item) {
cache.add(item); // Добавляем, но никогда не удаляем!
}
Шаг 5: Fix
private static LoadingCache<String, String> cache =
CacheBuilder.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(...);
Пример 4: Race condition
Симптом: Случайные ошибки при высокой нагрузке
Шаг 1: Воспроизведи stress test
ab -n 10000 -c 100 http://localhost:8080/api/transfer
# Результаты нестабильны
# Иногда: 200 OK
# Иногда: 500 Error
Шаг 2: Добавь debugging
public void transfer(Long fromId, Long toId, BigDecimal amount) {
logger.info("Transfer start: from={}, to={}, amount={}", fromId, toId, amount);
Account from = getAccount(fromId);
logger.info("From balance: {}", from.getBalance());
Account to = getAccount(toId);
// Здесь может быть race condition
Thread.sleep(100); // Искусственная задержка для воспроизведения
from.setBalance(from.getBalance().subtract(amount));
save(from);
}
Шаг 3: Видишь проблему? Два потока читают одинаковый баланс, оба отнимают, один перезаписывает.
Шаг 4: Fix
@Transactional(isolation = Isolation.SERIALIZABLE)
public void transfer(...) {
// Теперь thread-safe
}
Debugging Tips & Tricks
1. Bisect метод (binary search для bug-а)
Если не знаешь где баг:
- Comment out половину кода
- Работает ли?
- Если да: баг во второй половине
- Если нет: баг в первой половине
- Повтори до конца
2. Add logging strategically
logger.debug("Starting processing");
logger.debug("Step 1 done, result: {}", result);
logger.debug("Step 2 done, result: {}", result);
logger.error("Processing failed", exception);
3. Use assertions в development
assert amount.compareTo(BigDecimal.ZERO) > 0 : "Amount must be positive";
assert account != null : "Account not found";
4. Unit tests для reproduction
@Test
void testNegativeAmount() {
// Reproduce bug
assertThrows(IllegalArgumentException.class, () ->
service.transfer(account1, account2, BigDecimal.valueOf(-100))
);
}
Мой процесс
- Сохраняю спокойствие — паника не помогает
- Понимаю масштаб — это production? critical?
- Собираю информацию — логи, метрики, воспроизведение
- Формирую гипотезы — обычно это 3-5 вариантов
- Тестирую — один за другим
- Документирую — для future reference
- Делаю post-mortem — как предотвратить в future
Главный вывод
Debugging это skill, которым нужно владеть как Senior Developer. Это умение отличает хорошего разработчика от отличного.