개인 프로젝트를 진행하면서, 처음에 계획했던 기능들을 다 구현하고 무엇을 더 해야할까라는 고민이 들었다.
더 기능을 추가하는 것보다는 지금까지 작성한 코드들에 대해서 리펙토링과 코드 개선을 하고 싶었다.
따라서 SonarQube를 정적분석을 하고 그 결과를 바탕으로 개선시키려고 한다.!
m1 docker 환경에서 sonarqube 이미지를 pull받고 실행시켰으며, 프로젝트에 jacoco와 sonarqube 의존성을 추가해주었다.
처음 sonarqube로 정적분석을 해본 결과
다음과 같은 결과가 나왔다. 생각보다 많은 Bugs, Security Hotspots, Code Smell이 발견되었다.
대부분
해당 문제들을 하나하나 리펙토링을 하면서 개인 프로젝트를 개선하였다. 해당 포스팅에서는 Bugs에 대한 문제들을 개선한 내용을 기록한다.
대부분 HTML파일에서 Bugs가 많이 발견되었으며, 발견된 Bugs는 아래 5가지이다. 모든 html에서 중복으로 발생한 Bugs가 많아 빠르게 수정할 수 있었다.
thymeleaf template layout을 사용하여 공통 page를 fragment, layout형식으로 조립하며 html을 작성하고 있다. 따라서 공통 page에 <!DOCTPYE>와 <title>가 포함되어 있어 나머지 html 파일에는 추가하지 않는데 이 부분이 Bugs로 판별되었다. 따라서 다음과 같이 모든 html파일에 <!DOCTPYE>와 <title>를 추가하였다.
<!DOCTYPE>
<html layout:decorate="~{layout}" lang="ko" xml:lang="ko" xmlns="http://www.w3.org/1999/html">
<head>
<title>ebook</title>
</head>
bootstrap icons을 사용하기 위해 <i>를 사용하였는데, Sonarqube에서는 <i>가 이텔릭체인 태그로 판단하고 <i>를 사용하는 것보다는 CSS를 사용하여 적용하는 것을 권장하였다. 따라서 이 Bugs를 해결하기 위해 <i>를 <span>로 변경하였으며, bootstrap icons 사용시 <i>가 아니더라도 잘 동작하는 것을 확인하였다.
"시각 장애가 있는 사용자가 접근할 수 있도록, 테이블에 데이터가 액세스하기 전에 콘텐츠에 대한 설명을 제공하는 것이 중요합니다." 라는 Sonarqube의 글이 있었다. 따라서 <table>의 설명을 추가하기 위해 <caption>를 추가하여 콘텐츠에 대한 설명을 제공하였다. 웹 접근성이라는 부분에 대해서 느낄 수 있어 흥미롭고 신기했다.!
<table class="table table-hover mt-3">
<caption>출금 신청 목록</caption>
<thead class="table-success">
<tr>
<th>번호</th>
<th>제목</th>
<th>작성자</th>
<th>작성날짜</th>
<th>수정날짜</th>
</tr>
</thead>
...
ResponseEntity<JsonNode> responseEntity = payService.confirmPayments(params, id, principal.getName());
if (responseEntity.getStatusCode() == HttpStatus.OK) {
return "redirect:/order/%s".formatted(id);
} else {
model.addAttribute("message", responseEntity.getBody().get("message").asText());
model.addAttribute("code", responseEntity.getBody().get("code").asText());
model.addAttribute("orderId", params.get("orderId"));
return "order/fail_order";
}
위 코드에서 아래 코드에 대해 Bugs가 발생하였다. 문제는 responseEntity의 body에 message와 code 값이 null일 수 있다는 Bugs이다. 해당 코드는 tosspayments API를 이용하는 코드였기에 응답에 당연히 API 명세대로 message와 code가 존재할 것이라 생각하여 아래와 같이 작성하였다. 하지만 이는 좋지 않는 방법이라는 것을 깨달았다. 따라서 null인지를 체크하는 과정을 위해 Objects.requireNonNull를 사용하였다.
// 수정전
model.addAttribute("message", responseEntity.getBody().get("message").asText());
model.addAttribute("code", responseEntity.getBody().get("code").asText());
// 수정후
model.addAttribute("message", Objects.requireNonNull(responseEntity.getBody()).get("message").asText());
model.addAttribute("code", Objects.requireNonNull(responseEntity.getBody()).get("code").asText());
그럼 여기서 Optional과 Objects.requireNonNull의 차이점이 궁금하였다.
두 문법 모두 NPE를 피하기 위함이라는 공통점이 존재하지만,
즉, Objects.requireNonNull는 API문서에 나온 그대로 메소드나 생성자의 파라미터 null 검증을 위해 설계된 메서드로 자주 사용된다고 한다.
다음에는 Security Hotspots를 해결한 과정을 포스팅해야겠다 ~