- README.md: no information on how to run and test the app.
- README.md: no information on minimum runtime requirements (Java 11+).
- Unused code:
getSurname()
,getLoans()
,setLoans(...)
etc. Best practice: never write/generate code "just in case". - Packaging: according to Common Closure Principle, classes that change together must be packaged together. So, instead of putting closely related classes in different packages (
exceptions
,models
,enums
,repositories
,requests
,services
), put them together or split by domain (lending
,client
). More info here. - API: the API tries to be, but is not RESTful. E.g. plurals should be used:
/loans
,/loans/{clientUUID}
- Architecture: domain model leaks to the API. Instead, we should decouple the domain model from our API/screens, because they change for different reasons. In practice, our APIs should always return DTOs, not entities.
- Exception handling: in Spring, you don't need to catch exceptions and manually convert them into appropriate http responses. You can do this declaratively. See
@ResponseStatus
annotation. - Instead of using reflection (
ReflectionTestUtils.setField
), make certain fields package-private and, optionally, mark them with annotation @VisibleForTesting. Or better – use constructor injection. - Tests are difficult to reason about, because they are not written as behaviour specifications. See book
Growing Object-Oriented Software Guided by Tests
. - Not all critical functionality is covered by tests (e.g. catch blocks in
applyForLoan
). - Repository must not be responsible for object creation:
findByPersonalIdOrNew
. - Echo chambers:
client.assignLoan(loan)
vs.client.assign(loan)
,ApplyForLoan.loanAmount
vs.ApplyForLoan.amount
- Getters could have been replaced with simple nouns (e.g.
client.status()
) - Getters are setters are not needed in data structures, because public fields are enough. (e.g.
ApplyForLoan
) - Code smell: null arguments (e.g.
loanController.applyForLoan(loanApplication, null)
) - Architecture smell: test/dev code leaks in production code. (
@Value("${localDev.mockIp:}") String ipOverwrite;
or@Value("${api.notASecret}")
). - Anemic domain model:
Client
andLoans
are just data bags, not objects. - Misused Optionals:
return country.isPresent() ? country.get() : new Country();
Usecountry.orElseGet(Country::new);
instead. - Variable type inference is not used when appropriate (e.g.
HttpRequest request = HttpRequest.newBuilder()
could have been justvar request = HttpRequest.newBuilder()
) - CountryResolver over-engineered. Simpler version is here. Could have been better with Retrofit library.
- Unnecessary serialVersionUIDs.
if (ClientStatus.BLACKLISTED.equals(loan.getClient().getStatus())) { ... }
->if (client.isBlacklisted()) { ... }
- Tests don't catch critical bugs (changing TIMEFRAME_IN_DAYS from 2 to 1);
- Where fast unit tests are enough, slow Spring component tests are used.
- Deprecated Date/Calendar API usage: use Java 8 date (LocalDate, LocalDateTime) instead of Date/Calendar.
- There is no need for CountryRepository. LoanRepository would suffice.
- Very generic names:
if (count >= MAX_REQUESTS) { ... }
("count", "MAX_REQUESTS") assertTrue(loanRepo.count() == 1);
should beassertEquals(loanRepo.count(), 1);
- Long train wrecks: assertEquals("EE",
loanRepo.findAll().iterator().next().getCountry().getCode()
); Could have been solved by introducing aloan
variable. Moreover, it's better to extend from JpaRepository than CrudRepository, because its methods return Lists, not Iterators. List<Borrower> findByPersonalId(String personalId);
could returnOptional<Borrower>
LoanApplication
entity is missing; FieldsappliedAt
andcountry
do not belong toLoan
. AlsoBorrower
, notClient
.- IValidationRule could have been replaced with
Specification
pattern.
Last active
February 26, 2021 21:05
-
-
Save sizovs/d00c5ba8958b1b5b64a232799ff01a67 to your computer and use it in GitHub Desktop.
RaunoReview.md
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment