Обзор кода в финансовом сервисе

В коде есть ошибки компиляции, денежной точности и бизнес-логики: const для объектов недопустим, Double ненадёжен для денег, тарифные if пересекаются, а исключение отправки чека скрывается.

Короткий ответ

Что ответить на собеседовании

В коде есть ошибки компиляции, денежной точности и бизнес-логики: const для объектов недопустим, Double ненадёжен для денег, тарифные if пересекаются, а исключение отправки чека скрывается.

Подробный разбор

Ответ с пояснениями

Условие

Вообрази, что ты старший разработчик в финтех‑компании, а младший коллега прислал тебе модуль для проверки. Тебе необходимо выполнить ревью кода.

const val receiptRestClient = ReceiptRestClient()
const val cbrRestClient = CbrRestClient()

@Service
class PaymentService {
    
    @Autowired
    private var paymentRepository: PaymentRepository? = null

    @Autowired
    private lateinit var userRepository: UserRepository

    @Transactional
    fun doPayment(
        amount: Double,
        currency: Currency,
        recipientId: Long?,
        userId: Long
    ) {
        val amountInRub: Double = amount * cbrRestClient.getRates().get(currency.getCode())

        val user: User = userRepository.findUserById(userId)!!

        val payment: Payment = Payment(amountInRub, user, recipientId)
        paymentRepository!!.save(payment)

        if (amountInRub < 1000) {
            val fee = Fee(amountInRub * 0.015, user)
            paymentRepository!!.saveFee(fee)
        }

        if (amountInRub > 1000) {
            val fee = Fee(amountInRub * 0.01, user)
            paymentRepository!!.saveFee(fee)
        }

        if (amountInRub > 5000) {
            val fee = Fee(amountInRub * 0.005, user)
            paymentRepository!!.saveFee(fee)
        }

        try {
            receiptRestClient.notify(payment)
        } catch (t: Throwable) {
            // do nothing
        }
    }
}

Ответ

В коде есть ошибки компиляции, денежной точности и бизнес-логики: const для объектов недопустим, Double ненадёжен для денег, тарифные if пересекаются, а исключение отправки чека скрывается.

Используйте constructor injection и BigDecimal либо целые минимальные денежные единицы с явным округлением. При amountInRub = 1000 комиссия не создаётся, а при сумме > 5000 создаются две комиссии; границы диапазонов сначала согласуйте и выразите одной таблицей правил или when. Проверяйте сумму, валюту, получателя, права пользователя и наличие курса без !!.\n\nВнешний вызов курса не должен неоправданно удерживать DB-транзакцию. Сохраняйте использованный курс и идемпотентный идентификатор платежа. Уведомление о чеке доставляйте через outbox с повтором, не проглатывайте Throwable. Запись строки Payment в БД сама по себе ещё не доказывает успешное движение денег.

ИИ-помощник для собеседований

Хочешь уверенно проходить собеседования?

Попробуй ИИ-помощник для собеседований: слышит вас и собеседника, анализирует экран, подсказывает ответы в реальном времени, работает без VPN и не попадает в захват экрана.

Подробнее