이전 포스팅에서 스크럼 팀을 구성하는 3가지 핵심 역할과 책임을 다루 었다. 그렇다면 이들은 구체적으로 실무에서 어떻게 일할까?

 

https://hsunnystory.tistory.com/264

 

[Software Engineering] 애자일(Agile) 스크럼 기초: PO, SM, 개발팀의 명확한 실무 R&R

이전 포스팅([Software Enginnering] 애자일 선언문과 12가지 원칙)에서 강조했듯, 지라(Jira)를 쓰고 스프린트를 도입한다고 해서 곧바로 애자일 팀이 되는 것은 아니다. 시스템 구조가 바뀌려면 근본적

hsunnystory.tistory.com

 

애자일이 그저 "문서 없이 빨리빨리 개발하자"는 무법 지대가 되지 않도록, 스크럼은 아주 명확하고 규칙적인 이벤트와 산출물을 정의하고 있다. 이번 포스팅에서는 스크럼의 핵심 프로세스들이 어떻게 굴러가는지, 그리고 각 이벤트가 우리의 아키텍처와 코드 품질에 어떤 영향을 미치는지 살펴본다.

 

1. 스크럼의 심장, 스프린트 (Spring)

스프린트는 보통 1주~4주(실무에서는 2주가 가장 흔함)의 고정된 기간을 가진다. 워터폴 방식이 6개월짜리 거대한 마라톤이라면, 스크럼은 2주짜리 단거리 전력 질주를 반복하며 프로덕트를 점진적으로 완성해 나가는 구조다.

 

스프린트의 가장 큰 특징은 타임박싱이다. 정해진 기간이 끝나면 기획했던 기능이 100% 완성되지 않았더라도 미련 없이 스프린트를 종료하고 다음 사이클로 넘어간다. 이는 완벽주의에 빠져 일정이 무한정 지연되는 것을 원칙적으로 차단한다.

 

2. 스크럼의 3가지 핵심 산출물 (Artifacts)

팀이 무엇을 해야 할지, 그리도 현재 상태가 어떤지 시각적으로 보여주는 3가지 중요한 기준점이 존재한다.

 

프로덕트 백로그 (Procude Backlog)

 - 프로덕트에 필요한 모든 요구사항의 총집합이다. PO가 관리하며, 비즈니스 가치가 높은 순서대로 정렬된다.

 

스프린트 백로그 (Sprint Backlog)

 - 이번 스프린트(예:2주) 동안 개발팀이 완료하기로 약속한 작업들의 목록이다.

 - 프로덕트 백로그의 상위 항목들을 가져와, 개발자가 실제로 코드를 짤 수 있는 수준의 기술적인 테스트(Task)로 잘게 쪼갠다.

(예: 로그인 기능 -> OAuth2 인증 API 구현, User 테이블 스키마 설계)

 

인크리먼트 (Increment)

 - 스프린트가 끝날 때마다 완성되는 실제로 동작하는 소프트웨어의 조각이다.

 - 여기서 가장 중요한 개념이 완료의 정의(Definition of Done, DoD)다. 기준이 명확해야 한다. 

(예: 내 로컬에서 잘 도는게 아니라 단위 테스트 커버리지 80% 달성, QA 통과, 스테이징 배포 완료까지가 완료라는 팀 차원의 합의가 필수적이다.)

3. 스크럼의 4가지 핵심 이벤트 (Events)

스프린트라는 2주의 타잉ㅁ박스 안에는 팀의 싱크를 맞추기 위한 4가지 필수 이벤트가 규칙적으로 발생한다.

 

스프린트 플래닝 (Sprint Planning)

 - 목적 : 이번 스프린트에 무엇을, 어떻게 만들것 인가?

 - 개발팀은 비즈니스 목표를 달성하기 위해 필요한 백엔드 아키텍쳐, DB 마이그레이션 전략 등을 논의하고, 팀이 소화할 수 있는 작업량만큼만 스프린트 백로그를 가져온다. 개발자를 무리한 일정 압박으로부터 보호하는 방파제 역할을 한다.

 

데일리 스크럼 (Daily Scrum)

 - 목적 : 매일 15분, 진행 상황을 공유하고 이슈를 식별한다.

 - 어제 한 일, 오늘 할 일, 그리고 방해 요소를 짧게 공유한다.

 

스프린트 리뷰 (Sprint Review)

 - 목적 : 스프린트 마지막에 완성된 인크리먼트(동작하는 소프트웨어)를 PO와 타 부서에 시연하고 피드백을 받는다. PPT가 아닌 실제 API나 화면으로 보여준다.

 

스프린트 회고 (Sprint Retrospective)

 - 목적 : 스크럼에서 가장 중요한 이벤트, 프로덕트가 아니라 우리가 일하는 방식을 회고하고 개선한다.

 - "이번에 배포 과정에서 DB 락 이슈로 롤백을 했는데, 다음 스프린트부터는 DDL 변경 시나리오 리뷰를 프로세스에 추가하자"와 같은 실질적인 액션 아이템(Action Item)을 도출한다.

 

4. [Bas vs Good Code] 완료의 정의(Dod)가 무너질 때 생기는 비극

개발자에게 애자일 환경에서 가장 위험한 순간은, 스프린트 일정을 맞추기 위해 기능만 동작하게 억지로 끼워 맞출 때다. 테스트 코드 작성이나 보안 정책 적용완료의 정의(DoD)에 포함되어 있지 않다면, 기술 부채는 눈덩이처럼 불어난다.

 

유저의 비밀번호를 변경하는 API를 예로 들어보자.

 

[Bad Code] DoD를 무시한 일정 맞추기용 스크립트

시간이 없다는 이유로 컨트롤러에서 직접 DB를 찌르고 비밀번호를 평문으로 저장해버린다. 단위 테스트는 생략되었다. 기능은 당장 동작하겠지만, 이는 다음 스프린트에서 심각한 보안 이슈와 버그 폭탄으로 되돌아온다.

 

@RestController
@RequiredArgsConstructor
public class UserController {
    
    private final UserRepository userRepository;

    // 트랜잭션 처리, 암호화, 검증 로직이 컨트롤러에 무분별하게 섞여 있음
    @PostMapping("/api/users/{id}/password")
    public ResponseEntity<Void> changePassword(@PathVariable Long id, @RequestBody String newPassword) {
        User user = userRepository.findById(id).orElseThrow();
        
        // 치명적 결함: 암호화 적용 안 됨, 패스워드 정책 검증 없음
        user.setPassword(newPassword); 
        userRepository.save(user);
        
        return ResponseEntity.ok().build();
    }
}

 

[Good Code] 엄격한 DoD를 통과한 견고한 설계

성숙한 스크럼 팀은 일정이 빡빡하더라도 보안 로직 적용과 단위 테스트 통과를 완료의 정의(DoD)로 삼고 이를 반드시 지켜낸다. 객체지향 설계를 준수하며, 안전한 코드를 인도한다.

 

// 1. 책임을 분리한 도메인/서비스 로직
@Service
@RequiredArgsConstructor
public class UserService {
    
    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder; // Security 암호화 인터페이스 위임

    @Transactional
    public void changePassword(Long userId, String rawPassword) {
        User user = userRepository.findById(userId)
            .orElseThrow(() -> new IllegalArgumentException("User not found"));
            
        // 패스워드 정책 검증 (별도의 정책 클래스로 분리하는 것이 이상적)
        validatePasswordPolicy(rawPassword);
        
        // 암호화 책임을 PasswordEncoder에게 위임하고, 도메인 객체 상태 변경
        user.changePassword(passwordEncoder.encode(rawPassword));
    }
}

// 2. 외부 요청 처리만 담당하는 깔끔한 컨트롤러
@RestController
@RequiredArgsConstructor
public class UserController {
    
    private final UserService userService;

    @PostMapping("/api/users/{id}/password")
    public ResponseEntity<Void> changePassword(@PathVariable Long id, 
                                             @RequestBody PasswordChangeRequest request) {
        // 비즈니스 로직은 서비스 레이어로 완전히 위임
        userService.changePassword(id, request.getNewPassword());
        return ResponseEntity.ok().build();
    }
}

 

스프린트 일정을 맞추는 것도 중요하지만, 엔지니어링의 본질인 품질을 타협하여 얻어낸 속도는 결국 다음 스프린트의 발목을 잡는 가짜 속도일 뿐이다.

 

5. 결론

스크럼의 이벤트와 산출물은 팀을 감시하기 위한 마이크로 매니지먼트 도구가 아니라, 팀을 보호하고 잠재적인 리스크를 가시화하기 위한 시스템이다.

 

단순히 주어지는 백로그를 수동적으로 쳐내는 데 그쳐서는 안 된다. 플래닝 시간에 "이 비즈니스 요구사항을 제대로 구현하려면 기존의 낡은 결제 테이블 구조를 리팩토링해야 하므로, 이번 스프린트 백로그에 기술 부채 해결 테스크를 포함해야 합니다." 라고 당당히 요구할 수 있어야 한다. 눈에 보이지 않는 백엔드의 기술적 요소들을 스프린트 안으로 끌어와 가시화하는 것, 그것이 시니어 엔지니어가 스크럼에 기여하는 가장 핵심적인 역할이다.

+ Recent posts