
회사에 입사하고 나서 많은 것들을 배우고 있지만, 특히 코드 작성에 대해 다른 팀원 분들의 좋은 코드 작성 방법을 학습하려고 노력했다.
그리고 현재까지 개선해온 코드 작성 습관에 대해 정리해보고자 한다.
사실 클래스, 메서드, 변수, .. etc 등과 같이 이름을 지을 때는 당연히 직관적으로 짓는거 아냐? 라는 생각이 들 수 있다.
하지만, 내가 실제로 마주했던 상황은 다음과 같은 케이스였다.
예를 들어, 서버별로 다르게 가져야 하는 값이 있다고 가정해보자.
보다 상황을 구체화해보자면, B2C 와 같다.
A 회사에서 개발하고 제공하는 서비스 중 특정 기능을 화면 단에서 보여줄 때 1-2-3 과 같은 depth 로 노출시켜준다고 생각해보자.
이때, A 회사에서 해당 서비스를 B 회사로 판매해서 제공할 때 B 회사에서는 특정 기능을 1-2-3 이 아닌 1-3-2 / 2-1-3 / .. etc 등 자체적으로 커스텀해서 다르게 화면에서 노출시켜줄 수 있다.
이때, 해당 값을 DB의 특정 테이블 - 컬럼에서 조회해온다고 한다면 결국 회사의 서버별로 다른 값을 가져야 한다고 볼 수 있다.
나의 경우, 관례적으로 해당 테이블의 컬럼 이름과 동일한 필드를 담은 Response Dto 를 작성했었다.
public record ResponseDto(
String name,
Sring nameValue,
String nameAlias,
..
String reference1,
String reference2,
..
) { .. }
해당 테이블의 용도와 각 컬럼이 무엇을 의미하는지 알고 있었기에, 무의식적으로 위와 같이 이름이 동일한 방향으로 작성했었다.
하지만, '각 컬럼이 무엇을 의미하는지 나타냈으면'이라는 팀원의 코드 리뷰를 통해 실제로 화면 단에서 해당 필드들이 어떻게 사용되는지를 기반으로 리팩토링을 진행할 수 있었다.
public record ResponseDto(
String majorCategory, // name
String categoryValue, // nameValue
String categoryAlias, // nameAlias
..
String iconName, // reference1
String popupPageUsage, // reference2
..
) { .. }
이 내용 또한 사람에 따라 당연한 내용이라고 볼 수 있지만, 이전에는 감에 의존해서 메서드 분리를 진행했다면 이제는 기준을 설명할 수 있게 되었다.
다음 예시를 기반으로 이해해보자.
회사 에는 여러 유저 가 존재하고, 해당 유저는 권한을 가질 수 있다.
이때, 권한에는 A / B / C , .. etc 와 같이 종류가 존재한다.
그리고 권한 엔티티는 A / B / C 를 각각 n개씩 가질 수 있으므로, 조회 시 이점을 누리기 위해 연관 관계 매핑이 되어 있다면 다음과 같다.
public class RoleEntity {
..
@OneToMany(fetch = FetchType.LAZY)
@JoinColumn(name = "roleId")
private List<RoleA> roleAs,
@OneToMany(fetch = FetchType.LAZY)
@JoinColumn(name = "roleId")
private List<RoleB> roleBs,
@OneToMany(fetch = FetchType.LAZY)
@JoinColumn(name = "roleId")
private List<RoleC> roleCs
..
}
이때, 다음과 같은 요구 사항을 가진 API 를 개발해야 한다면?
사용자는 권한에 대한 여러 카테고리 (ex. A / B / C) 에 대한 항목들을 한 번의 요청 시마다 추가 또는 삭제를 진행할 수 있다.
다양한 개발 방향이 있겠지만, 나는 @PostMapping Request 에 delete -> add 를 하는 방법으로 구현했다.
각 권한에 대한 카테고리의 데이터 양이 전적으로 많지 않았기에 편의성에 중점을 두었기 때문이다.
그럼 전체 코드 흐름은 다음과 같이 구성된다.
@Transactional
public void createRoleDetail(
UUID roleId,
List<RoleADto> roleAs,
List<RoleBDto> roleBs,
List<RoleCDto> roleCs
) {
// 1. 각 Request Dto 에 대한 validate
// 2. Role 조회
// 3. 각 Role Category 에 대한 id 추출
// 4. id 를 기반으로 조회 시 데이터가 존재한다면, 각 Category Entity 삭제
// 5. 저장
}
이때, 해당 API 의 주된 역할은 저장 이라고 볼 수 있다.
그 이유인 즉슨, 삭제는 권한에 대한 id 를 기반으로, 조회했을 때 존재하는 경우 진행하는 조건이 있는 action이기 때문이다.
따라서 위 경우에 대해서는 삭제에 대한 코드에 대해 메서드 분리를 진행했다.
사실 3번 내용이 메인이라고 볼 수 있다.
바로 예시를 통해 이해해보자.
아주 간단한 예시로 위에서 이야기한 권한을 기반으로 들어보자.
권한은 생성, 수정, 권한 카테고리 수정 등 여러 행위가 이뤄질 수 있는 도메인이다.
이때, 코드를 작성해본다면 다음과 같이 구현 할 수 있다.
@Transactional
public void createRole(
CreateRoleRequest request
) {
// 1. request validate
// 2. role 생성
Role role = Role.builder()
.roleId( .. )
.build();
// 3. role 저장
roleRepository.save(role);
}
@Transactional
public void updateRole(
UpdateRoleRequest request
) {
// 1. request validate
// 2. 기존 role 조회
Role role = roleRepository.findById(request.roleId())
.orElseThrow(() -> new EntityNotFoundException("Role not found"));
// 3. request 와 기존에 존재하던 role 을 바탕으로 update 할 role 생성
Role updatingRole = Role.builder()
.roleId(role.roleId)
. ..
.bulid();
// 4. 저장
roleRepository.save(updatingRole);
}
이때, Role 이라고 하는 도메인에 집중된 행위는 생성과 수정을 진행하는 2번과 3번에 해당하는 코드라고 볼 수 있다.
따라서 해당 코드들을 Role class (= domain class) 내부에 넣고, Service class 에는 메서드명을 통해서 흐름을 파악할 수 있게 하는 정도로 작성할 수 있다.
@Transactional
public void createRole(
CreateRoleRequest request
) {
// 1. request validate 및 Role 생성
Role role = Role.create(
request.roleId(),
..
); // Builder pattern이 domain class 내부로 이전 ❗️
// 2. Role 저장
roleRepository.save(role);
}
@Entity
@Table(name = "role")
@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Role extends BaseEntity implements Serializable {
@Id
@Column(name = "role_id")
private UUID id;
public static Role create(
CreateRoleRequest request
) {
// 1. request validate
GlobalValidator.validateNull(request);
// 2. Role 생성
return Role.builder()
. ..
.build();
}
즉, 3 layer Architecture 에 맞게 - 그 중 Service layer 에는 비즈니스 로직의 흐름만 compact 하게 남기는 것이 핵심이라고 볼 수 있다.
이 밖에도 내용이 더 있지만, 최신순으로 작성하다보니 복기가 좀 필요할 것 같아 글을 분리허도록 하겠다.
개발자에게 코드는 곧 말이고, 이를 얼마나 잘 다듬어서 표현하느냐에 따라 context 없이도 어떻게 빠르게 잘 이해할 수 있도록 도울 수 있는지 느끼는 요즘이다.
Good job!