<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Seon's IT Story</title>
    <link>https://hsunnystory.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sun, 17 May 2026 20:24:55 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>H.Sunny,,</managingEditor>
    <image>
      <title>Seon's IT Story</title>
      <url>https://t1.daumcdn.net/cfile/tistory/99D7C5415B62B5C923</url>
      <link>https://hsunnystory.tistory.com</link>
    </image>
    <item>
      <title>[Software Enginnering] 애자일(Agile) 스크럼 실전: 이벤트와 산출물</title>
      <link>https://hsunnystory.tistory.com/265</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이전 포스팅에서 스크럼 팀을 구성하는 3가지 핵심 역할과 책임을 다루 었다. 그렇다면 이들은 구체적으로 실무에서 어떻게 일할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://hsunnystory.tistory.com/264&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://hsunnystory.tistory.com/264&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1778463945712&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Software Engineering] 애자일(Agile) 스크럼 기초: PO, SM, 개발팀의 명확한 실무 R&amp;amp;R&quot; data-og-description=&quot;이전 포스팅([Software Enginnering] 애자일 선언문과 12가지 원칙)에서 강조했듯, 지라(Jira)를 쓰고 스프린트를 도입한다고 해서 곧바로 애자일 팀이 되는 것은 아니다. 시스템 구조가 바뀌려면 근본적&quot; data-og-host=&quot;hsunnystory.tistory.com&quot; data-og-source-url=&quot;https://hsunnystory.tistory.com/264&quot; data-og-url=&quot;https://hsunnystory.tistory.com/264&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bR5bzA/dJMb9fZzN8s/7RFStKHbc4yrLNMMoOu9aK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/biSA6D/dJMb9fZzN8r/TkK77SKkXbtBrTCjNVqfsK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://hsunnystory.tistory.com/264&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hsunnystory.tistory.com/264&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bR5bzA/dJMb9fZzN8s/7RFStKHbc4yrLNMMoOu9aK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/biSA6D/dJMb9fZzN8r/TkK77SKkXbtBrTCjNVqfsK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Software Engineering] 애자일(Agile) 스크럼 기초: PO, SM, 개발팀의 명확한 실무 R&amp;amp;R&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이전 포스팅([Software Enginnering] 애자일 선언문과 12가지 원칙)에서 강조했듯, 지라(Jira)를 쓰고 스프린트를 도입한다고 해서 곧바로 애자일 팀이 되는 것은 아니다. 시스템 구조가 바뀌려면 근본적&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hsunnystory.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애자일이 그저 &quot;문서 없이 빨리빨리 개발하자&quot;는 무법 지대가 되지 않도록, 스크럼은 아주 명확하고 규칙적인 이벤트와 산출물을 정의하고 있다. 이번 포스팅에서는 스크럼의 핵심 프로세스들이 어떻게 굴러가는지, 그리고 각 이벤트가 우리의 아키텍처와 코드 품질에 어떤 영향을 미치는지 살펴본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 스크럼의 심장, 스프린트 (Spring)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프린트는 보통 1주~4주(실무에서는 2주가 가장 흔함)의 고정된 기간을 가진다. 워터폴 방식이 6개월짜리 거대한 마라톤이라면, 스크럼은 2주짜리 단거리 전력 질주를 반복하며 프로덕트를 점진적으로 완성해 나가는 구조다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프린트의 가장 큰 특징은 타임박싱이다. 정해진 기간이 끝나면 기획했던 기능이 100% 완성되지 않았더라도 미련 없이 스프린트를 종료하고 다음 사이클로 넘어간다. 이는 완벽주의에 빠져 일정이 무한정 지연되는 것을 원칙적으로 차단한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 스크럼의 3가지 핵심 산출물 (Artifacts)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팀이 무엇을 해야 할지, 그리도 현재 상태가 어떤지 시각적으로 보여주는 3가지 중요한 기준점이 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;프로덕트 백로그 (Procude Backlog)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 프로덕트에 필요한 &lt;b&gt;모든 요구사항의 총집합&lt;/b&gt;이다. PO가 관리하며, 비즈니스 가치가 높은 순서대로 정렬된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;스프린트 백로그 (Sprint Backlog)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;이번 스프린트(예:2주) 동안 개발팀이 완료하기로 약속한 작업들의 목록&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 프로덕트 백로그의 상위 항목들을 가져와, 개발자가 실제로 코드를 짤 수 있는 수준의 기술적인 테스트(Task)로 잘게 쪼갠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(예: 로그인 기능 -&amp;gt; OAuth2 인증 API 구현, User 테이블 스키마 설계)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;인크리먼트 (Increment)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 스프린트가 끝날 때마다 완성되는 &lt;b&gt;실제로 동작하는 소프트웨어의 조각&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 여기서 가장 중요한 개념이 &lt;b&gt;완료의 정의(Definition of Done, DoD)&lt;/b&gt;다. 기준이 명확해야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(예: 내 로컬에서 잘 도는게 아니라 단위 테스트 커버리지 80% 달성, QA 통과, 스테이징 배포 완료까지가 완료라는 팀 차원의 합의가 필수적이다.)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 스크럼의 4가지 핵심 이벤트 (Events)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프린트라는 2주의 타잉ㅁ박스 안에는 팀의 싱크를 맞추기 위한 4가지 필수 이벤트가 규칙적으로 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;스프린트 플래닝 (Sprint Planning)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;목적&lt;/b&gt; : 이번 스프린트에 무엇을, 어떻게 만들것 인가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 개발팀은 비즈니스 목표를 달성하기 위해 필요한 백엔드 아키텍쳐, DB 마이그레이션 전략 등을 논의하고, 팀이 소화할 수 있는 작업량만큼만 스프린트 백로그를 가져온다. 개발자를 무리한 일정 압박으로부터 보호하는 방파제 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;데일리 스크럼 (Daily Scrum)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;목적&lt;/b&gt; : 매일 15분, 진행 상황을 공유하고 이슈를 식별한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 어제 한 일, 오늘 할 일, 그리고 방해 요소를 짧게 공유한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;스프린트 리뷰 (Sprint Review)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;목적&lt;/b&gt; : 스프린트 마지막에 완성된 인크리먼트(동작하는 소프트웨어)를 PO와 타 부서에 시연하고 피드백을 받는다. PPT가 아닌 실제 API나 화면으로 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;스프린트 회고 (Sprint Retrospective)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;목적&lt;/b&gt; : 스크럼에서 가장 중요한 이벤트, 프로덕트가 아니라 &lt;b&gt;우리가 일하는 방식&lt;/b&gt;을 회고하고 개선한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &quot;이번에 배포 과정에서 DB 락 이슈로 롤백을 했는데, 다음 스프린트부터는 DDL 변경 시나리오 리뷰를 프로세스에 추가하자&quot;와 같은 실질적인 액션 아이템(Action Item)을 도출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. [Bas vs Good Code] 완료의 정의(Dod)가 무너질 때 생기는 비극&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자에게 애자일 환경에서 가장 위험한 순간은, 스프린트 일정을 맞추기 위해 기능만 동작하게 억지로 끼워 맞출 때다. &lt;b&gt;테스트 코드 작성&lt;/b&gt;이나 &lt;b&gt;보안 정책 적용&lt;/b&gt;이 &lt;b&gt;완료의 정의(DoD)&lt;/b&gt;에 포함되어 있지 않다면, 기술 부채는 눈덩이처럼 불어난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저의 비밀번호를 변경하는 API를 예로 들어보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[Bad Code] DoD를 무시한 일정 맞추기용 스크립트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간이 없다는 이유로 컨트롤러에서 직접 DB를 찌르고 비밀번호를 평문으로 저장해버린다. 단위 테스트는 생략되었다. 기능은 당장 동작하겠지만, 이는 다음 스프린트에서 심각한 보안 이슈와 버그 폭탄으로 되돌아온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@RestController &lt;br /&gt;@RequiredArgsConstructor &lt;br /&gt;public&amp;nbsp;class&amp;nbsp;UserController&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;UserRepository&amp;nbsp;userRepository; &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;트랜잭션&amp;nbsp;처리,&amp;nbsp;암호화,&amp;nbsp;검증&amp;nbsp;로직이&amp;nbsp;컨트롤러에&amp;nbsp;무분별하게&amp;nbsp;섞여&amp;nbsp;있음 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@PostMapping(&quot;/api/users/{id}/password&quot;) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;ResponseEntity&amp;lt;Void&amp;gt;&amp;nbsp;changePassword(@PathVariable&amp;nbsp;Long&amp;nbsp;id,&amp;nbsp;@RequestBody&amp;nbsp;String&amp;nbsp;newPassword)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;User&amp;nbsp;user&amp;nbsp;=&amp;nbsp;userRepository.findById(id).orElseThrow(); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;치명적&amp;nbsp;결함:&amp;nbsp;암호화&amp;nbsp;적용&amp;nbsp;안&amp;nbsp;됨,&amp;nbsp;패스워드&amp;nbsp;정책&amp;nbsp;검증&amp;nbsp;없음 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;user.setPassword(newPassword);&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;userRepository.save(user); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;ResponseEntity.ok().build(); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[Good Code] 엄격한 DoD를 통과한 견고한 설계&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성숙한 스크럼 팀은 일정이 빡빡하더라도 보안 로직 적용과 단위 테스트 통과를 완료의 정의(DoD)로 삼고 이를 반드시 지켜낸다. 객체지향 설계를 준수하며, 안전한 코드를 인도한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;//&amp;nbsp;1.&amp;nbsp;책임을&amp;nbsp;분리한&amp;nbsp;도메인/서비스&amp;nbsp;로직 &lt;br /&gt;@Service &lt;br /&gt;@RequiredArgsConstructor &lt;br /&gt;public&amp;nbsp;class&amp;nbsp;UserService&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;UserRepository&amp;nbsp;userRepository; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;PasswordEncoder&amp;nbsp;passwordEncoder;&amp;nbsp;//&amp;nbsp;Security&amp;nbsp;암호화&amp;nbsp;인터페이스&amp;nbsp;위임 &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Transactional &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;changePassword(Long&amp;nbsp;userId,&amp;nbsp;String&amp;nbsp;rawPassword)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;User&amp;nbsp;user&amp;nbsp;=&amp;nbsp;userRepository.findById(userId) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.orElseThrow(()&amp;nbsp;-&amp;gt;&amp;nbsp;new&amp;nbsp;IllegalArgumentException(&quot;User&amp;nbsp;not&amp;nbsp;found&quot;)); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;패스워드&amp;nbsp;정책&amp;nbsp;검증&amp;nbsp;(별도의&amp;nbsp;정책&amp;nbsp;클래스로&amp;nbsp;분리하는&amp;nbsp;것이&amp;nbsp;이상적) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;validatePasswordPolicy(rawPassword); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;암호화&amp;nbsp;책임을&amp;nbsp;PasswordEncoder에게&amp;nbsp;위임하고,&amp;nbsp;도메인&amp;nbsp;객체&amp;nbsp;상태&amp;nbsp;변경 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;user.changePassword(passwordEncoder.encode(rawPassword)); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;//&amp;nbsp;2.&amp;nbsp;외부&amp;nbsp;요청&amp;nbsp;처리만&amp;nbsp;담당하는&amp;nbsp;깔끔한&amp;nbsp;컨트롤러 &lt;br /&gt;@RestController &lt;br /&gt;@RequiredArgsConstructor &lt;br /&gt;public&amp;nbsp;class&amp;nbsp;UserController&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;UserService&amp;nbsp;userService; &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@PostMapping(&quot;/api/users/{id}/password&quot;) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;ResponseEntity&amp;lt;Void&amp;gt;&amp;nbsp;changePassword(@PathVariable&amp;nbsp;Long&amp;nbsp;id,&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@RequestBody&amp;nbsp;PasswordChangeRequest&amp;nbsp;request)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;비즈니스&amp;nbsp;로직은&amp;nbsp;서비스&amp;nbsp;레이어로&amp;nbsp;완전히&amp;nbsp;위임 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;userService.changePassword(id,&amp;nbsp;request.getNewPassword()); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;ResponseEntity.ok().build(); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프린트 일정을 맞추는 것도 중요하지만, 엔지니어링의 본질인 품질을 타협하여 얻어낸 속도는 결국 다음 스프린트의 발목을 잡는 가짜 속도일 뿐이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스크럼의 이벤트와 산출물은 팀을 감시하기 위한 마이크로 매니지먼트 도구가 아니라, &lt;b&gt;팀을 보호하고 잠재적인 리스크를 가시화하기 위한 시스템&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 주어지는 백로그를 수동적으로 쳐내는 데 그쳐서는 안 된다. 플래닝 시간에 &quot;이 비즈니스 요구사항을 제대로 구현하려면 기존의 낡은 결제 테이블 구조를 리팩토링해야 하므로, 이번 스프린트 백로그에 기술 부채 해결 테스크를 포함해야 합니다.&quot; 라고 당당히 요구할 수 있어야 한다. 눈에 보이지 않는 백엔드의 기술적 요소들을 스프린트 안으로 끌어와 가시화하는 것, 그것이 시니어 엔지니어가 스크럼에 기여하는 가장 핵심적인 역할이다.&lt;/p&gt;</description>
      <category>Software Engineering</category>
      <category>agile</category>
      <category>scrum</category>
      <category>Sprint</category>
      <category>스크럼</category>
      <category>스프린트</category>
      <category>애자일</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/265</guid>
      <comments>https://hsunnystory.tistory.com/265#entry265comment</comments>
      <pubDate>Mon, 11 May 2026 17:11:43 +0900</pubDate>
    </item>
    <item>
      <title>[Software Engineering] 애자일(Agile) 스크럼 기초: PO, SM, 개발팀의 명확한 실무 R&amp;amp;R</title>
      <link>https://hsunnystory.tistory.com/264</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이전 포스팅([Software Enginnering] 애자일 선언문과 12가지 원칙)에서 강조했듯, 지라(Jira)를 쓰고 스프린트를 도입한다고 해서 곧바로 애자일 팀이 되는 것은 아니다. 시스템 구조가 바뀌려면 근본적으로 팀원들이 일하고 협업하는 구조(R&amp;amp;R)가 먼저 변해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잦은 요구사항 변경을 전제로 하는 애자일 환경에서 전통적인 수직적 역할 분담은 병목만 유발한다. 이번 포스팅에서는 애자일의 대표적인 프레임워크인 스크럼(Scrum)의 3가지 핵심 역할과, 비즈니스 담당자와의 올바른 협업이 실제 코드와 아키텍처에 어떤 영향을 미치는지 살펴본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 스크럼의 3가지 핵심 역할 (Roles)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스크럼은 복잡한 문제를 해결하기 위해 고안된 경량 프레임워크다. 스크럼 팀에는 지시를 내리는 팀장이나 전통적인 PM이 존재하지 않으며, 역할과 권한이 명확히 분산되어 상호 견제와 균형을 이룬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;프로덕트 오너(Product Owner, PO)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;핵심 역할&lt;/b&gt;: 무엇을 만들 것인가를 결정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 프로덕트의 비즈니스 가치를 극대화하는 책임을 지며, 요구사항이 담긴 프로덕트 백로그(Product Backlog)의 유일한 관리자다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 개발팀에게 우선순위가 높은 비즈니스 목표를 제시하지만, 그것을 어떻게 기술적으로 구현할지, 일정을 얼마나 잡을지에 대해서는 지시할 권한이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;스크럼 마스터(Scrum Master, SM)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;핵심 역할&lt;/b&gt;: 팀이 스크럼 원칙을 잘 지키도록 돕는 서번트 리더(Servant Leader)이자 촉진자다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 팀의 개발 속도를 저해하는 외부의 방해 요소를 차단하고, 매끄러운 커뮤니케이션 환경을 조성한다. 관리하는 것이 아니라 코칭하는 역할이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;개발팀(Developers)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;핵심 역할&lt;/b&gt;: &lt;b&gt;어떻게&lt;/b&gt; 만들 것인가를 결정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 기획, 디자인, 프론트엔드, 백엔드 등 프로덕트를 실제로 구동하게 만드는 모든 작업자를 포함한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- PO가 제시한 목표를 달성하기 위한 기술적 아키텍처, 코드 품질, 데이터베이스 설계 등에 대한&lt;b&gt; 절대적인 권한과 책임&lt;/b&gt;을 가진다. 스스로 작업량을 추정(Estimate)하고 일정을 계획한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 전통적 PM vs 스크럼&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 PM은 일정과 리소스 관리에 초점을 맞추는 탑다운 방식이 흔했다. 반면, 애자일 스크럼에서는 각자의 전문성을 바탕으로 권한이 분리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;PO&lt;/b&gt;: &quot;비즈니스적으로 A 기능이 가장 시급하고 가치가 높습니다.&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;개발팀&lt;/b&gt;: &quot;A 기능을 안정적으로 구현하려면 레거시 테이블 분리를 포함해 1 스프린트(2주)가 필요합니다. 동시성 이슈를 막기 위해 분산 락을 적용하겠습니다.&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;SM&lt;/b&gt;: &quot;PO님, 개발팀이 이번 스프린트에 온전히 집중할 수 있도록 타 부서의 갑작스러운 데이터 추출 요청은 제가 막아두겠습니다.&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. [Bad vs Good Code] 역할 분담이 아키텍처에 미치는 영향&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스크럼 환경에서 개발팀은 기획서를 단순히 코드로 번역하는 하청업체가 아니다. 개발자는 PO와 지속적으로 소통하며 비즈니스 도메인을 깊이 이해해야 한다. &lt;b&gt;PO의 비즈니스 언어가 코드에 그대로 투영되어야만 요구사항 변경에 유연하게 대처할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배송 완료 후 7일 이내에만 환불이 가능하다는 PO의 요구사항을 예로 들어보자. 도메인에 대한 고민 없이 수동적으로 코딩하는 팀과, 비즈니스 규칙을 객체에 적극적으로 녹여내는 애자일 팀의 코드는 완전히 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[Bad Code] 수동적인 팀의 절차지향적 서비스 스크립트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도메인 지식을 서비스 계층에 if문으로 흩뿌려 놓은 상태다. PO가 7일이 아니라 14일로 늘려주시고, VVIP 회원은 30일까지 가능하게 해주세요 라고 정책변경을 요청하면, 개발자는 이 거대한 OrderSerivce를 뒤져서 로직을 뜯어고쳐야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Service &lt;br /&gt;@RequiredArgsConstructor &lt;br /&gt;public&amp;nbsp;class&amp;nbsp;OrderService&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;OrderRepository&amp;nbsp;orderRepository; &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;비즈니스&amp;nbsp;룰이&amp;nbsp;서비스&amp;nbsp;로직에&amp;nbsp;절차적으로&amp;nbsp;노출되어&amp;nbsp;있음 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Transactional &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;processRefund(Long&amp;nbsp;orderId)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Order&amp;nbsp;order&amp;nbsp;=&amp;nbsp;orderRepository.findById(orderId).orElseThrow(); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;데이터만&amp;nbsp;가져와서&amp;nbsp;서비스&amp;nbsp;계층에서&amp;nbsp;직접&amp;nbsp;상태와&amp;nbsp;날짜를&amp;nbsp;계산&amp;nbsp;(응집도&amp;nbsp;하락) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(order.getStatus()&amp;nbsp;!=&amp;nbsp;OrderStatus.SHIPPED)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw&amp;nbsp;new&amp;nbsp;IllegalStateException(&quot;배송&amp;nbsp;완료&amp;nbsp;상태가&amp;nbsp;아닙니다.&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;long&amp;nbsp;daysBetween&amp;nbsp;=&amp;nbsp;ChronoUnit.DAYS.between(order.getShippedDate(),&amp;nbsp;LocalDateTime.now()); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(daysBetween&amp;nbsp;&amp;gt;&amp;nbsp;7)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw&amp;nbsp;new&amp;nbsp;IllegalStateException(&quot;환불&amp;nbsp;가능&amp;nbsp;기간(7일)이&amp;nbsp;지났습니다.&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;order.setStatus(OrderStatus.REFUNDED); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[Good Code] 능동적인 애자일 팀의 도메인 주도 설계(DDD)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;건강한 스크럼 팀의 개발자는 PO와 소통하여 환불 가능 여부 라는 핵심 비즈니스 규칙을 Order라는 도메인 객체 내부로 캡슐화 한다. 서비스 계층은 그저 도메인 객체에게 메시지를 던질 뿐이다. 정책이 변경되어도 Order 객체 내부만 수정하면 되므로 안전하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;//&amp;nbsp;1.&amp;nbsp;핵심&amp;nbsp;비즈니스&amp;nbsp;규칙을&amp;nbsp;품고&amp;nbsp;있는&amp;nbsp;풍부한&amp;nbsp;도메인&amp;nbsp;모델&amp;nbsp;(Rich&amp;nbsp;Domain&amp;nbsp;Model) &lt;br /&gt;@Entity &lt;br /&gt;public&amp;nbsp;class&amp;nbsp;Order&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Enumerated(EnumType.STRING) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;OrderStatus&amp;nbsp;status; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;LocalDateTime&amp;nbsp;shippedDate; &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;PO와&amp;nbsp;정의한&amp;nbsp;비즈니스&amp;nbsp;언어를&amp;nbsp;메서드명과&amp;nbsp;로직으로&amp;nbsp;정확히&amp;nbsp;표현 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;refund()&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(!isRefundable())&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw&amp;nbsp;new&amp;nbsp;IllegalStateException(&quot;환불이&amp;nbsp;불가능한&amp;nbsp;주문입니다.&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.status&amp;nbsp;=&amp;nbsp;OrderStatus.REFUNDED; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;환불&amp;nbsp;정책이&amp;nbsp;변경되면&amp;nbsp;이&amp;nbsp;메서드&amp;nbsp;내부만&amp;nbsp;수정하면&amp;nbsp;됨&amp;nbsp;(OCP&amp;nbsp;준수) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;boolean&amp;nbsp;isRefundable()&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(this.status&amp;nbsp;!=&amp;nbsp;OrderStatus.SHIPPED)&amp;nbsp;return&amp;nbsp;false; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;long&amp;nbsp;daysAfterShipping&amp;nbsp;=&amp;nbsp;ChronoUnit.DAYS.between(this.shippedDate,&amp;nbsp;LocalDateTime.now()); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;daysAfterShipping&amp;nbsp;&amp;lt;=&amp;nbsp;7;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;//&amp;nbsp;2.&amp;nbsp;도메인&amp;nbsp;객체를&amp;nbsp;호출하기만&amp;nbsp;하는&amp;nbsp;깔끔한&amp;nbsp;서비스&amp;nbsp;계층 &lt;br /&gt;@Service &lt;br /&gt;@RequiredArgsConstructor &lt;br /&gt;public&amp;nbsp;class&amp;nbsp;OrderService&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;OrderRepository&amp;nbsp;orderRepository; &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Transactional &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;processRefund(Long&amp;nbsp;orderId)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Order&amp;nbsp;order&amp;nbsp;=&amp;nbsp;orderRepository.findById(orderId) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.orElseThrow(()&amp;nbsp;-&amp;gt;&amp;nbsp;new&amp;nbsp;IllegalArgumentException(&quot;주문을&amp;nbsp;찾을&amp;nbsp;수&amp;nbsp;없습니다.&quot;)); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;캡슐화된&amp;nbsp;도메인&amp;nbsp;로직(비즈니스&amp;nbsp;룰)을&amp;nbsp;호출하기만&amp;nbsp;함.&amp;nbsp;&quot;환불해라!&quot; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;order.refund();&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국, 스크럼에서 PO와 개발팀이 밀접하게 소통해야 하는 이유는 단순히 친해지기 위해서가 아니다. &lt;b&gt;비즈니스의 복잡성을 기술적인 도메인 모델로 완벽하게 매핑하여, 향후 쏟아질 요구사항 변경에 흔들리지 않는 견고한 아키텍처를 만들기 위함이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스크럼을 도입했다고 해서 갑자기 모든 문제가 마법처럼 해결되지는 않는다. 개발자가 과거 워터폴 시절처럼 PO가 시키는 대로 코드만 짰습니다 라며 수동적인 태도를 취하거나, 반대로 기획적인 영역까지 개발팀이 독단적으로 결정하려 한다면 그 팀은 무너질 수 밖에 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- PO의 권한을 존중하라: 어떤 기능이 사용자에게 더 큰 가치를 주는가에 대한 우선순위 결정은 전적으로 PO의 영역이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 기술의 통제권을 사수하라: 비즈니스 목표를 이루기 위한 테이블 설계 구조, 객체지향 패턴의 적용, 기술 부채를 해결하기 위한 리팩토링 일정 분배는 철저히 개발팀이 주도하고 책임져야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직급이나 호칭이 바뀌는 것이 애자일이 아니다. 각자의 역할과 책임에 맞게 전문가로서 권한을 행사하고 책임을 지는 구조, 그것이 진짜 폭발적인 퍼포먼스를 내는 스크럼 팀의 모습이다.&lt;/p&gt;</description>
      <category>Software Engineering</category>
      <category>agile</category>
      <category>DDD</category>
      <category>PO</category>
      <category>scrum</category>
      <category>SM</category>
      <category>도메인주도설계</category>
      <category>스크럼</category>
      <category>스크럼마스터</category>
      <category>애자일</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/264</guid>
      <comments>https://hsunnystory.tistory.com/264#entry264comment</comments>
      <pubDate>Fri, 8 May 2026 17:05:35 +0900</pubDate>
    </item>
    <item>
      <title>[Git] 브랜치 이동 시 남은 유령 폴더 해결법 : git clean -fd</title>
      <link>https://hsunnystory.tistory.com/263</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서 기능 개발을 위해 feature/A 브랜치에서 한참 작업하다가, 급한 버그 픽스를 위해 hotfix/B 브랜치로 switch 또는 checkout 한 경험이 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 분명 B 브랜치에는 존재하지 않는 A 브랜치 전용 폴더와 파일들이 내 IDE나 작업 디렉토리에 그대로 남아있는 것응ㄹ 발견하게 된다. 코드를 빌드하려니 이 폴더들 때문에 컴파일 에러가 발생하거나 롬복(Lombok), 빌드 캐시가 꼬이는 일도 부지기수다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도대체 왜 B 브랜치에는 없는 폴더가 내 로컬에 남아있는 것이며, 이를 어떻게 깔끔하게 청소해야 할까? 이번 포스팅에서는 &lt;b&gt;Git이 작업 디렉토리를 다루는 철학과 git clean의 명확한 사용법&lt;/b&gt;에 대해 알아본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 왜 유령 폴더가 남아 있을까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Git의 브랜치 이동 메커니즘을 이해하려면, Git의 최우선 철학인 &lt;b&gt;사용자의 데이터를 함부로 날리지 않는다(안전 우선)&lt;/b&gt;를 알아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A 브랜치에서 B 브랜치로 넘어갈 때, Git은 &lt;b&gt;추적 중인(Tracked)&lt;/b&gt; 파일들은 모두 B 브랜치의 상태로 완벽하게 동기화(변경, 삭제, 생성)한다. &lt;b&gt;하지만 추적하지 않는(Untracked) 파일이나 폴더는 절대로 건드리지 않는다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 상황에서 유령 폴더가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;빌드 산출물&lt;/b&gt; : A 브랜치에서 코드를 실행하며 생성된 build/, out/, node_modules/ 등의 폴더&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;새로 만든 파일&lt;/b&gt; : A 브랜치에서 폴더와 파일을 새로 만들었으나, 아직 git add나 git commit을 하지 않은 상태에서 B 브랜치로 넘어간 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;B에서는 지워진 폴더&lt;/b&gt; : A 브랜치에서는 관리되던 폴더가 B 브랜치에서는 삭제되었는데, 로컬 디렉토리 내부에 Git이 모르는(Untracked) 파일이 단 하나라도 남아있는 경우, GIt은 그 파일을 보호하기 위해 부모 폴더 전체를 남겨둔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 해결책 : git clean&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 Untracked 파일과 폴더를 작업 디렉토리에서 완전히 쓸어버리는 명령어가 바로 &lt;b&gt;git clean&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;git clean -fd (가장 많이 쓰이는 표준)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-f (force) : 강제로 삭제를 실행한다.(Git 설정에 따라 -f 없이 clean을 입력하면 안전을 위해 거부된다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-d (directory) : Untracked 파일뿐만 아니라 &lt;b&gt;Untracked 폴더(디렉토리)&lt;/b&gt;까지 모두 지운다. (이 옵션이 없으면 폴더는 껍데기로 남는다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;git clean -fdf (또는 -ff -d)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-fdf는 f가 두 번 들어간 형태다. 보통 -fd로 충분하지만, Git에서 -f 를 두번( -ff ) 주면 &lt;b&gt;내부에 .git 폴더를 가진 하위 디렉토리(Submodule 등)까지 강제로 지워버리겠다&lt;/b&gt;는 아주 강력한 의미가 된다. 일반적인 상황이라면 -fd만으로 충분하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;git clean -xfd (초강력 초기화)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-x : .gitingore에 등록되어 무시되고 있는 파일들(예: 빌드 결과물, IDE 설정 파일 등)까지 전부 삭제한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;-&amp;gt; 브랜치를 바꿨는데 빌드가 꼬여서 자꾸 알 수 없는 에러가 날 때, 프로젝트를 처음 git clone 받은 완전히 깨끗한 상태로 되돌리기 위해 사용&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 주의사항 : 한번 수행되면 복구할 수 없다!&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;git clean은 Git의 추적 범위 밖에 있는 파일들을 지우는 명령어다. 즉, &lt;b&gt;한 번 지우면&lt;/b&gt; Ctrl + z 나 &lt;b&gt;Git 명령어로 절대 복구할 수 없다.&lt;/b&gt; 작성 중이던 중요한 메모 파일이나 신규 소스 코드가 날아가는 대참사가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 대참사를 막기 위해 무턱대고 -f를 누르지 않고, 반드시 가상 실행( -n )을 먼저 거치자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;#&amp;nbsp;1단계&lt;/b&gt;:&amp;nbsp;무엇이&amp;nbsp;지워질지&amp;nbsp;리스트만&amp;nbsp;먼저&amp;nbsp;확인한다&amp;nbsp;(Dry&amp;nbsp;Run) &lt;br /&gt;#&amp;nbsp;-n&amp;nbsp;(dry-run):&amp;nbsp;실제&amp;nbsp;삭제는&amp;nbsp;하지&amp;nbsp;않고,&amp;nbsp;삭제될&amp;nbsp;대상만&amp;nbsp;터미널에&amp;nbsp;출력 &lt;br /&gt;&lt;b&gt;git&amp;nbsp;clean&amp;nbsp;-nd&lt;/b&gt; &lt;br /&gt;&lt;br /&gt;&lt;b&gt;#&amp;nbsp;2단계&lt;/b&gt;:&amp;nbsp;출력된&amp;nbsp;리스트에&amp;nbsp;내가&amp;nbsp;작업&amp;nbsp;중이던&amp;nbsp;중요한&amp;nbsp;파일이&amp;nbsp;없는지&amp;nbsp;확인&amp;nbsp;후&amp;nbsp;실제&amp;nbsp;삭제 &lt;br /&gt;&lt;b&gt;git&amp;nbsp;clean&amp;nbsp;-fd&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브랜치를 이동할 때 남겨지는 유령 폴더는 Git이 버그를 낸 것이 아니라, &lt;b&gt;당신의 소중한 Untracked 데이터를 잃어버리지 않게 지켜주려는 Git의 배려&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 배려가 개발 환경을 꼬이게 만든다면, git clean -fd를 통해 불필요한 찌꺼기들을 확실하게 청소해 주자. 브랜치 이동 후 빌드가 꼬일 때는 삽질하지 말고 git clean -xfd로 로컬 환경을 초기화하는 것이 퇴근 시간을 앞당기는 최고의 지름길이다. 단, 엔터를 치기 전 -nd 옵션으로 한 번 더 확인하는 방어적 습관을 들이자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Git</category>
      <category>GIT</category>
      <category>Git Branch</category>
      <category>git checkout</category>
      <category>git clean</category>
      <category>git switch</category>
      <category>GitClean</category>
      <category>버전관리</category>
      <category>브랜치이동</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/263</guid>
      <comments>https://hsunnystory.tistory.com/263#entry263comment</comments>
      <pubDate>Thu, 7 May 2026 18:12:45 +0900</pubDate>
    </item>
    <item>
      <title>[Software Enginnering] 애자일 선언문과 12가지 원칙</title>
      <link>https://hsunnystory.tistory.com/262</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;애자일은 방법론이나 툴이 아니라, 일하는 방식의 철학이다. 이번 포스팅에서는 2001년 발표된 애자일 선언문과 12가지 원칙을 실무 관점에서 다시 해석해본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://hsunnystory.tistory.com/261&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://hsunnystory.tistory.com/261&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1777860031679&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Software Enginnering] 에자일(Agile) 이란?&quot; data-og-description=&quot;오픈을 한 달 앞둔 시점, 기획팀 혹은 고객사로부터 이런 피드백을 받은 경험이 한 번쯤은 있을 것이다. &amp;quot;이거 우리가 원했던 기능이 아닌데요????&amp;quot;이러한 비극은 왜 터지는 것일까? 이는 우리가 &quot; data-og-host=&quot;hsunnystory.tistory.com&quot; data-og-source-url=&quot;https://hsunnystory.tistory.com/261&quot; data-og-url=&quot;https://hsunnystory.tistory.com/261&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bLyiEH/dJMb8Z3u1vB/imjUZQVd4CFdvgkk3pt720/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/FEq5K/dJMb8Xki4Kb/2bEi8zPGhoWJDoLY3RfmG0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://hsunnystory.tistory.com/261&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hsunnystory.tistory.com/261&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bLyiEH/dJMb8Z3u1vB/imjUZQVd4CFdvgkk3pt720/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/FEq5K/dJMb8Xki4Kb/2bEi8zPGhoWJDoLY3RfmG0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Software Enginnering] 에자일(Agile) 이란?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;오픈을 한 달 앞둔 시점, 기획팀 혹은 고객사로부터 이런 피드백을 받은 경험이 한 번쯤은 있을 것이다. &quot;이거 우리가 원했던 기능이 아닌데요????&quot;이러한 비극은 왜 터지는 것일까? 이는 우리가&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hsunnystory.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 가치(Value)를 중심에 둔 애자일 선언문 4가지 핵심&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;공정과 도구보다 개인과 상호작용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jira 티켓에 댓글만 남기며 핑퐁을 치는 것보다 프론트엔드 개발자나 기획자와 화이트보드 앞에서 아키텍처를 논의하는 것이 훨씬 빠르고 정확하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;포괄적인 문서보다 작동하는 소프트웨어&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;100장짜리 완벽한 API 명세서를 작성하느라 시간을 쏟기보다, 핵심 기능만 동작하는 API를 빠르게 배포하고 Swagger를 통해 실시간으로 스펙을 맞춰나가는 것이 실무적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;계약 협상 보다 고객과의 협력&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방어적으로 코딩하는 대신, 비즈니스 목표 달성을 위해 시스템 구조를 어떻게 유연하게 가져갈지 함께 고민해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;계획을 따르기보다 변화에 대응&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 아키텍처 설계가 완벽할 것이라는 환상을 버려야 한다. 변화를 수용할 수 있는 확장성 있는 코드 베이스를 유지하는 것이 핵심이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 개발자가 명심해야 할 애자일 원칙 3가지&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;가치 있는 소프트웨어를 일찍, 그리고 지속적으로 전달한다.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 곧 &lt;b&gt;CI/CD 파이프라인의 구축&lt;/b&gt;을 의미한다. 개발자가 코드를 푸시하면 자동으로 테스트가 돌고 스테이징 서버에 배포되어야만 &lt;b&gt;지속적인 전달&lt;/b&gt;이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기술적 탁월함과 좋은 설계에 대한 지속적 관심이 기민성을 높인다.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애자일을 단순히 빨리 대충 개발하는 것으로 오해하면 안 된다. 탄탄한 객체지향 설계와 리팩토링, 그리고 테스트 코드가 뒷받침되지 않으면 기술 부채가 쌓여 결국 변화에 대응할 수 없는 레거시가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;단순성 - 안해도 될 일을 최대한 하지 않는 기술이 필수적이다.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미래에 발생할지도 모르는 오버 엔지니어링을 경계해야 한다. YAGNI(You Aren't Gonna Need It) 원칙에 따라 현재의 비즈니스 요구사항을 해결하는 가장 단순하고 우아한 구조를 선택해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.무늬만 애자일을 피하는 기술적 탁월함&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항이 매 스프린트마다 변경되는 애자일 환경에서, 기술적 탁월함이 결여된 코드는 어떻게 무너지는지, 그리고 어떻게 방어해야 하는지 살펴보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;할인정책(Discount Policy)이 수시로 변하는 이커머스 도메인을 가정해 보자. 기획팀은 이번 주에는 정액 할인을, 다음주에는 비율 할인을, 그 다음 주에는 특정 등급 전용할인을 테스트해보고 싶어 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[Bad Code : 기술적 탁월함이 결여된 Fake Agile]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 정책이 추가될 때마다 기존 OrderService 클래스의 코드를 직접 수정해야 한다. 사이드 이펙트가 두려워 배포를 주저하게 되고, 결국 요구사항 변경을 환영할 수 없는 상태가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Service &lt;br /&gt;public&amp;nbsp;class&amp;nbsp;OrderService&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;비즈니스&amp;nbsp;로직에&amp;nbsp;분기문(if-else)이&amp;nbsp;덕지덕지&amp;nbsp;붙기&amp;nbsp;시작함&amp;nbsp;(OCP&amp;nbsp;위반) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;long&amp;nbsp;calculatePrice(Order&amp;nbsp;order,&amp;nbsp;String&amp;nbsp;discountType)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;long&amp;nbsp;price&amp;nbsp;=&amp;nbsp;order.getOriginalPrice(); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(&quot;FIXED&quot;.equals(discountType))&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;price&amp;nbsp;-=&amp;nbsp;1000;&amp;nbsp;//&amp;nbsp;정액&amp;nbsp;할인 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;else&amp;nbsp;if&amp;nbsp;(&quot;PERCENT&quot;.equals(discountType))&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;price&amp;nbsp;=&amp;nbsp;price&amp;nbsp;-&amp;nbsp;(price&amp;nbsp;*&amp;nbsp;0.1);&amp;nbsp;//&amp;nbsp;10%&amp;nbsp;할인 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;else&amp;nbsp;if&amp;nbsp;(&quot;VIP&quot;.equals(discountType))&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;요구사항이&amp;nbsp;추가될&amp;nbsp;때마다&amp;nbsp;이&amp;nbsp;거대한&amp;nbsp;클래스를&amp;nbsp;수정해야&amp;nbsp;함 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;price&amp;nbsp;=&amp;nbsp;price&amp;nbsp;-&amp;nbsp;(price&amp;nbsp;*&amp;nbsp;0.2);&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;price; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Good Code: 변화를 환영하는 True Agile 아키텍처]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot의 DI와 다형성을 적극 활용한 전략 패턴(Strategy Pattern) 구조다. 새로운 할인 정책이 필요하다면 기존 코드는 단 한 줄도 건드리지 않고, 새로운 클래스만 추가하여 확장에 열려 있는(OCP) 시스템을 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;//&amp;nbsp;1.&amp;nbsp;추상화된&amp;nbsp;할인&amp;nbsp;인터페이스 &lt;br /&gt;public&amp;nbsp;interface&amp;nbsp;DiscountPolicy&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;boolean&amp;nbsp;isSatisfiedBy(String&amp;nbsp;discountType); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;long&amp;nbsp;applyDiscount(long&amp;nbsp;price); &lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;//&amp;nbsp;2.&amp;nbsp;개별&amp;nbsp;정책&amp;nbsp;구현&amp;nbsp;(새로운&amp;nbsp;정책은&amp;nbsp;새로운&amp;nbsp;클래스&amp;nbsp;추가만으로&amp;nbsp;해결) &lt;br /&gt;@Component &lt;br /&gt;public&amp;nbsp;class&amp;nbsp;FixedDiscountPolicy&amp;nbsp;implements&amp;nbsp;DiscountPolicy&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Override &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;boolean&amp;nbsp;isSatisfiedBy(String&amp;nbsp;type)&amp;nbsp;{&amp;nbsp;return&amp;nbsp;&quot;FIXED&quot;.equals(type);&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Override &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;long&amp;nbsp;applyDiscount(long&amp;nbsp;price)&amp;nbsp;{&amp;nbsp;return&amp;nbsp;price&amp;nbsp;-&amp;nbsp;1000;&amp;nbsp;} &lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;//&amp;nbsp;3.&amp;nbsp;핵심&amp;nbsp;비즈니스&amp;nbsp;로직&amp;nbsp;(변화로부터&amp;nbsp;격리됨) &lt;br /&gt;@Service &lt;br /&gt;@RequiredArgsConstructor &lt;br /&gt;public&amp;nbsp;class&amp;nbsp;OrderService&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Spring이&amp;nbsp;구현체&amp;nbsp;List를&amp;nbsp;런타임에&amp;nbsp;자동&amp;nbsp;주입 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;List&amp;lt;DiscountPolicy&amp;gt;&amp;nbsp;discountPolicies; &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;long&amp;nbsp;calculatePrice(Order&amp;nbsp;order,&amp;nbsp;String&amp;nbsp;discountType)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;discountPolicies.stream() &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.filter(policy&amp;nbsp;-&amp;gt;&amp;nbsp;policy.isSatisfiedBy(discountType)) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.findFirst() &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.map(policy&amp;nbsp;-&amp;gt;&amp;nbsp;policy.applyDiscount(order.getOriginalPrice())) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.orElse(order.getOriginalPrice());&amp;nbsp;//&amp;nbsp;일치하는&amp;nbsp;정책이&amp;nbsp;없으면&amp;nbsp;원가&amp;nbsp;반환 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안전한 테스트 코드와 위와 같은 클린 아키텍처가 존재할 때, 개발자는 기획팀의 요구사항 변경을 스트레스가 아닌 &lt;b&gt;프로덕트의 성장을 위한 자연스러운 실험&lt;/b&gt;으로 받아들일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애자일은 기획자나 스크럼 마스터만의 전유물이 아니다. 훌륭한 프로세스를 도입하더라도, 개밭팀의 코드가 스파게티처럼 얽혀있다면 그 팀은 절대 애자일해질 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무늬만 애자일을 피하고 진정한 기민성을 얻고 싶다면, Jira의 티켓 상태를 업데이트하는 것만큼이나 코드를 리팩토링하고, 자동화된 테스트를 작성하며, 아키텍처의 결합도를 낮추는 일(엔지니어링 프랙티스)에 팀의 리소스를 투자해야 한다. 시스템의 구조가 유연해야만 비즈니스도 유연해질 수 있다.&lt;/p&gt;</description>
      <category>Software Engineering</category>
      <category>agile</category>
      <category>FakeAgile</category>
      <category>애자일</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/262</guid>
      <comments>https://hsunnystory.tistory.com/262#entry262comment</comments>
      <pubDate>Mon, 4 May 2026 11:00:50 +0900</pubDate>
    </item>
    <item>
      <title>[Software Enginnering] 에자일(Agile) 이란?</title>
      <link>https://hsunnystory.tistory.com/261</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오픈을 한 달 앞둔 시점, 기획팀 혹은 고객사로부터 이런 피드백을 받은 경험이 한 번쯤은 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&amp;nbsp;&quot;이거 우리가 원했던 기능이 아닌데요????&quot;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 비극은 왜 터지는 것일까? 이는 우리가 여전히 요구사항이 변하지 않을 것이라 가정하는 &lt;b&gt;워터폴(Waterfall)&lt;/b&gt;속에서 개발하고 있기 때문이다. 이번 포스팅에서는 워터폴의 한계와 이를 극복하기 위한 에자일(Agile)의 본질에 대해 정리해본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://hsunnystory.tistory.com/260&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://hsunnystory.tistory.com/260&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1776304132544&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Software Enginnering] 워터폴(Waterfall) 방법론의 명과 암&quot; data-og-description=&quot;대형 SI 프로젝트나 공공기관 차세대 시스템 구축에 투입되면, 어김없이 거대한 엑셀 파일 두 개를 마주하게 된다. 수개월의 일정이 빼곡히 적힌 WBS와 수백 페이지에 달하는 요구사항 정의서다. &quot; data-og-host=&quot;hsunnystory.tistory.com&quot; data-og-source-url=&quot;https://hsunnystory.tistory.com/260&quot; data-og-url=&quot;https://hsunnystory.tistory.com/260&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bSYmK0/dJMb88F6tBa/WAyPU7LWFNOkt0zvdLUFf1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/pMkZZ/dJMb8T903pR/tJHTMQNZ2hkHbGNgYJtW91/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://hsunnystory.tistory.com/260&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hsunnystory.tistory.com/260&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bSYmK0/dJMb88F6tBa/WAyPU7LWFNOkt0zvdLUFf1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/pMkZZ/dJMb8T903pR/tJHTMQNZ2hkHbGNgYJtW91/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Software Enginnering] 워터폴(Waterfall) 방법론의 명과 암&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;대형 SI 프로젝트나 공공기관 차세대 시스템 구축에 투입되면, 어김없이 거대한 엑셀 파일 두 개를 마주하게 된다. 수개월의 일정이 빼곡히 적힌 WBS와 수백 페이지에 달하는 요구사항 정의서다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hsunnystory.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 에자일(Agile): 방법론이 아닌 &lt;b&gt;마인드셋&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에자일을 지라(Jira)를 쓰고, 아침마다 스탠드업 미팅을 하는 도구나 프로세스로 오해하는 경우가 많다. 하지만 에자일의 본질은 기법이 아니라 &lt;b&gt;변화를 수용하는 마인드셋&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애자일은 소프트웨어 개발의 불확실성을 인정한다. 처음부터 완벽한 설계는 없으므로, &lt;b&gt;동작하는 가장 작은 단위의 소프트웨어를 빠르게 만들어 고객에게 전달하고, 피드백을 받아 지속적으로 개선&lt;/b&gt;해 나가는 것이 핵심이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 애자일의 철학은 개발자가 작성하는 코드, 그리고 객체지향 설계의 원리와도 정확히 맞닿아 있다. 변화에 유연하게 대응하기 위해 우리는 코드 레벨에서 어떤 고민을 해야 할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 변화를 대하는 코드의 자세&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;워터폴 방식처럼 요구사항은 고정되어 있다고 가정하는 코드는 확장에 닫혀 있어 작은 변경에도 시스템 전체가 흔들린다. 반면, 애자일 방식의 코드는 다형성과 OCP를 활용해 변화를 부드럽게 수용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결제 시스템의 알림 기능을 예로 들어보자. 초기 요구사항은 &lt;b&gt;이메일 알림&lt;/b&gt; 하나 였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;[Bad Code : 워터풀 방식의 경직된 설계]&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 요구사항(SMS, 카카오톡 알림 등)이 추가될 때마다 핵심 비즈니스 로직인 OrderSerivce의 코드를 직접 수정해야 한다. 이는 변경에 취약하고 테스트하기 어려운 구조다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;//&amp;nbsp;핵심&amp;nbsp;비즈니스&amp;nbsp;로직이&amp;nbsp;구체&amp;nbsp;클래스에&amp;nbsp;강하게&amp;nbsp;결합되어&amp;nbsp;있음&amp;nbsp;(OCP&amp;nbsp;위반) &lt;br /&gt;public&amp;nbsp;class&amp;nbsp;OrderService&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;EmailSender&amp;nbsp;emailSender&amp;nbsp;=&amp;nbsp;new&amp;nbsp;EmailSender();&amp;nbsp;//&amp;nbsp;구체&amp;nbsp;클래스&amp;nbsp;의존 &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;completeOrder(Order&amp;nbsp;order)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;1.&amp;nbsp;주문&amp;nbsp;처리&amp;nbsp;로직 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;processPayment(order); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;2.&amp;nbsp;알림&amp;nbsp;발송&amp;nbsp;(요구사항이&amp;nbsp;변경되면&amp;nbsp;이곳을&amp;nbsp;직접&amp;nbsp;수정해야&amp;nbsp;함) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;emailSender.send(order.getUserEmail(),&amp;nbsp;&quot;주문이&amp;nbsp;완료되었습니다.&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;만약&amp;nbsp;SMS&amp;nbsp;알림이&amp;nbsp;추가된다면?&amp;nbsp;if문이&amp;nbsp;추가되고&amp;nbsp;코드가&amp;nbsp;뚱뚱해진다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Good Code: 에자일 방식의 유연한 설계]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에자일 환경에서는 요구사항이 언제든 변할 수 있음을 전제한다. 따라서 인터페이스에 의존하도록 설계하여, 새로운 알림 수단이 추가되더라도 기존 코드를 수정하지 안하고 확장만으로 대응할 수 있게 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;//&amp;nbsp;변화를&amp;nbsp;수용하는&amp;nbsp;인터페이스&amp;nbsp;설계 &lt;br /&gt;public&amp;nbsp;interface&amp;nbsp;NotificationSender&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;void&amp;nbsp;send(User&amp;nbsp;user,&amp;nbsp;String&amp;nbsp;message); &lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;public&amp;nbsp;class&amp;nbsp;EmailSender&amp;nbsp;implements&amp;nbsp;NotificationSender&amp;nbsp;{&amp;nbsp;/*&amp;nbsp;구현&amp;nbsp;생략&amp;nbsp;*/&amp;nbsp;} &lt;br /&gt;public&amp;nbsp;class&amp;nbsp;SmsSender&amp;nbsp;implements&amp;nbsp;NotificationSender&amp;nbsp;{&amp;nbsp;/*&amp;nbsp;구현&amp;nbsp;생략&amp;nbsp;*/&amp;nbsp;} &lt;br /&gt;&lt;br /&gt;//&amp;nbsp;핵심&amp;nbsp;비즈니스&amp;nbsp;로직&amp;nbsp;(수정에&amp;nbsp;닫혀&amp;nbsp;있고&amp;nbsp;확장에&amp;nbsp;열려&amp;nbsp;있음&amp;nbsp;-&amp;nbsp;OCP&amp;nbsp;준수) &lt;br /&gt;@Service &lt;br /&gt;@RequiredArgsConstructor &lt;br /&gt;public&amp;nbsp;class&amp;nbsp;OrderService&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;다형성을&amp;nbsp;활용하여&amp;nbsp;런타임에&amp;nbsp;전략을&amp;nbsp;주입받음 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;NotificationSender&amp;nbsp;notificationSender;&amp;nbsp; &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;completeOrder(Order&amp;nbsp;order)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;1.&amp;nbsp;주문&amp;nbsp;처리&amp;nbsp;로직 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;processPayment(order); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;2.&amp;nbsp;알림&amp;nbsp;발송&amp;nbsp;(알림의&amp;nbsp;종류가&amp;nbsp;SMS든&amp;nbsp;카카오톡이든&amp;nbsp;OrderService는&amp;nbsp;변경되지&amp;nbsp;않음) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;notificationSender.send(order.getUser(),&amp;nbsp;&quot;주문이&amp;nbsp;완료되었습니다.&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 코드를 유연하게 짜는 객체지향 설계와 일하는 방식을 유연하게 가져가는 애자일은 &lt;b&gt;변화에 대한 비용을 최소화 한다&lt;/b&gt;는 동일한 본질을 공유한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. IT기업에서 애자일을 중요시 하는 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서 비즈니스 환경은 매일 바뀐다. 경쟁사는 새로운 기능을 출시하고, 유저의 트렌드는 급변한다. 이런 상황에서 6개월 뒤에나 결과를 볼 수 있는 워터폴 방식은 비즈니스의 사형선고나 다름 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;리스크 최소화&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6개월간 개발해서 실패하는 것보다, 2주 만에 핵심 기능만 배포해 보고 고객 반응이 없으면 빠르게 방향을 트는 것이 훨씬 안전하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;지속적 가치 전달&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MSA나 CI/CD 파이프라인 같은 현대적인 백엔드 인프라 기술들은 모두 &lt;b&gt;하루에도 몇 번씩 안전하고 빠르게 배포한다&lt;/b&gt;는 애자일 철학을 기술적으로 뒷받침하기 위해 탄생한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애자일은 개발을 빨리하는 방법이 아니다. &lt;b&gt;올바른 방향으로 개발하고 있는지 지속적으로 확인하는 방법&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Software Engineering</category>
      <category>agile</category>
      <category>waterfall</category>
      <category>애자일</category>
      <category>워터폴</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/261</guid>
      <comments>https://hsunnystory.tistory.com/261#entry261comment</comments>
      <pubDate>Thu, 16 Apr 2026 13:26:10 +0900</pubDate>
    </item>
    <item>
      <title>[Software Enginnering] 워터폴(Waterfall) 방법론의 명과 암</title>
      <link>https://hsunnystory.tistory.com/260</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;대형 SI 프로젝트나 공공기관 차세대 시스템 구축에 투입되면, 어김없이 거대한 엑셀 파일 두 개를 마주하게 된다. 수개월의 일정이 빼곡히 적힌 WBS와 수백 페이지에 달하는 요구사항 정의서다. 폭포수처럼 위에서 아래로 흐르는 워터폴(Waterfall) 방법론의 본질과, 이 구조 속에서 개발자가 맞닥뜨리는 치명적인 문제, 그리고 이를 방어하기 위한 객체지향적 아키텍처 설계 전략에 대해 알아본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 워터폴(Waterfall) 방법론이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;워터폴 방법론은 소프트웨어 개발 생명주기(SDLC)를 순차적인 단계로 나누어 진행하는 고전적인 방식이다. 물이 폭포에서 아래로 떨어지듯, 한 단계가 완전히 끝나야만 다음 단계로 넘어갈 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;진행단계&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항 분석 -&amp;gt; 시스템 설계 -&amp;gt; 구현 -&amp;gt; 테스트 -&amp;gt; 배포 및 유지보수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용 이유&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관리자 입장에서 프로젝트의 진척도를 퍼센트로 측정하기 쉽고, 각 단계별 산출물이 명확하게 남기 때문에 계약 기반의 외주 프로젝트에서 주로 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. BDUF의 착각과 UAT(인수 테스트)의 재앙&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;워터폴 방법론은 하나의 거대한 전제를 깔고 있다. 바로 &lt;b&gt;요구사항은 프로젝트 초기에 완벽하게 정의될 수 있다는 믿음, 즉 BDUF(Big Design Up Front)&lt;/b&gt;다. 하지만 현실의 비즈니스는 끊임없이 변하며, 고객은 눈으로 완성된 화면을 보기 전까지 자신이 무엇을 원하는지 정확히 알지 못한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;피드백의 지연&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발의 90% 이상 완료된 후반부 통합 테스트나 UAT(사용자 인수 테스트) 단계에서야 고객이 결과물을 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;빅뱅(Big Bang) 리스크&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뒤늦게 부정적인 피드백을 받거나 핵심 아키텍처의 성능 결함이 발견되면, 이미 설계와 DB 스키마가 굳어진 상태로 시스템 전체를 뒤엎어야 하는 대 참사가 발생한다. 폭포수는 거꾸로 흐를 수 없기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 워터폴의 경직성을 방어하는 코드 설계 전략&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;요구사항 변경은 필연적&lt;/b&gt;이므로, 후반부에 쏟아질 변경 요청의 충격을 흡수할 수 있도록 코드를 유연하게 설계해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Bad Code: 기획서만 믿고 짠 절차 지향적 하드코딩&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기획서에 명시된 일반고객과 VIP 고객의 할인 정책만을 보고, 서비스 로직 내부에 제어문을 강하게 결합시킨 최악의 코드다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Service &lt;br /&gt;public&amp;nbsp;class&amp;nbsp;OrderService&amp;nbsp;{ &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;문제:&amp;nbsp;오픈&amp;nbsp;1주일&amp;nbsp;전,&amp;nbsp;기획팀에서&amp;nbsp;&quot;VVIP&amp;nbsp;등급과&amp;nbsp;신규&amp;nbsp;가입자&amp;nbsp;전용&amp;nbsp;할인을&amp;nbsp;추가해&amp;nbsp;주세요&quot;라고&amp;nbsp;한다면? &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;이&amp;nbsp;클래스의&amp;nbsp;코드를&amp;nbsp;직접&amp;nbsp;수정해야&amp;nbsp;하며,&amp;nbsp;이는&amp;nbsp;OCP(개방-폐쇄&amp;nbsp;원칙)&amp;nbsp;위반이다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;int&amp;nbsp;calculateDiscount(Member&amp;nbsp;member,&amp;nbsp;int&amp;nbsp;price)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(member.getGrade()&amp;nbsp;==&amp;nbsp;Grade.VIP)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;price&amp;nbsp;-&amp;nbsp;1000; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;else&amp;nbsp;if&amp;nbsp;(member.getGrade()&amp;nbsp;==&amp;nbsp;Grade.NORMAL)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;price;&amp;nbsp;//&amp;nbsp;할인&amp;nbsp;없음 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;끝없는&amp;nbsp;else&amp;nbsp;if의&amp;nbsp;늪... &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;price; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Good Code: 다형성을 활용한 변경에 열려있는 설계&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;워터폴의 폭탄(막판 요구사항 변경)을 방어하기 위해서는 비즈니스 로직을 추상화하여 OCP를 준수해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;//&amp;nbsp;1.&amp;nbsp;변하는&amp;nbsp;것(할인&amp;nbsp;정책)을&amp;nbsp;인터페이스로&amp;nbsp;추상화한다. &lt;br /&gt;public&amp;nbsp;interface&amp;nbsp;DiscountPolicy&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;int&amp;nbsp;discount(Member&amp;nbsp;member,&amp;nbsp;int&amp;nbsp;price); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;boolean&amp;nbsp;isSupport(Grade&amp;nbsp;grade); &lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;//&amp;nbsp;2.&amp;nbsp;구체적인&amp;nbsp;할인&amp;nbsp;정책을&amp;nbsp;구현한다.&amp;nbsp;(새로운&amp;nbsp;정책이&amp;nbsp;추가되어도&amp;nbsp;기존&amp;nbsp;코드는&amp;nbsp;수정되지&amp;nbsp;않는다) &lt;br /&gt;@Component &lt;br /&gt;public&amp;nbsp;class&amp;nbsp;VipDiscountPolicy&amp;nbsp;implements&amp;nbsp;DiscountPolicy&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Override &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;int&amp;nbsp;discount(Member&amp;nbsp;member,&amp;nbsp;int&amp;nbsp;price)&amp;nbsp;{&amp;nbsp;return&amp;nbsp;price&amp;nbsp;-&amp;nbsp;1000;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Override &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;boolean&amp;nbsp;isSupport(Grade&amp;nbsp;grade)&amp;nbsp;{&amp;nbsp;return&amp;nbsp;grade&amp;nbsp;==&amp;nbsp;Grade.VIP;&amp;nbsp;} &lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;@Service &lt;br /&gt;@RequiredArgsConstructor &lt;br /&gt;public&amp;nbsp;class&amp;nbsp;OrderService&amp;nbsp;{ &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;3.&amp;nbsp;Spring의&amp;nbsp;List&amp;nbsp;주입을&amp;nbsp;통해&amp;nbsp;모든&amp;nbsp;할인&amp;nbsp;정책을&amp;nbsp;동적으로&amp;nbsp;관리한다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;List&amp;lt;DiscountPolicy&amp;gt;&amp;nbsp;discountPolicies; &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;int&amp;nbsp;calculateDiscount(Member&amp;nbsp;member,&amp;nbsp;int&amp;nbsp;price)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;discountPolicies.stream() &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.filter(policy&amp;nbsp;-&amp;gt;&amp;nbsp;policy.isSupport(member.getGrade())) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.findFirst() &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.map(policy&amp;nbsp;-&amp;gt;&amp;nbsp;policy.discount(member,&amp;nbsp;price)) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.orElse(price);&amp;nbsp;//&amp;nbsp;해당하는&amp;nbsp;정책이&amp;nbsp;없으면&amp;nbsp;원가&amp;nbsp;반환 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;아키텍처적 이점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;워터폴 막바지에 새로운 고객 등급이나 할인 정책이 추가되더라도, OrderSerivce의 핵심 로직은 단 한 줄도 수정할 필요가 없다. 그저 DiscountPolicy를 구현한 새로운 클래스만 하나 추가하면 스프링이 알아서 의존성을 주입하고 분기 처리를 완료한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;워터폴 방법론 자체가 절대적인 악은 아니다. 우주선 제어 시스템이나 의료 기기 소프트웨어처럼 요규사항이 절대로 변해서는 안 되고 치밀한 사전 검증이 필요한 도메인에서는 여전히 최고의 선택이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 요구사항 변화가 극심한 현대의 웹/서버 비즈니스 환경에서는 워터폴의 경직성이 독이 되는 경우가 많다. 만약 현재 워터폴 기반의 프로젝트를 수행중이라면, 방법론의 한계를 명확히 인지하고 &lt;b&gt;인터페이스와 다형성을 활용하여 언제든 기획이 뒤집힐 수 있다는 전제하에 아키텍처를 설계&lt;/b&gt;해야 한다. 방법론이 코드를 보호해 주지 못한다면, 개발자는 유연한 설계로 스스로를 보호해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Software Engineering</category>
      <category>SDLC</category>
      <category>waterfall</category>
      <category>객체지향</category>
      <category>방법론</category>
      <category>워터폴</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/260</guid>
      <comments>https://hsunnystory.tistory.com/260#entry260comment</comments>
      <pubDate>Mon, 13 Apr 2026 10:56:09 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Boot] Relaxed Binding란</title>
      <link>https://hsunnystory.tistory.com/259</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이전 포스팅에서 5가지 네이밍 컨벤션과 데이터 매핑 전략에 대해 알아보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://hsunnystory.tistory.com/258&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://hsunnystory.tistory.com/258&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1775709900371&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Clean Code] 실무에서 알아야할 5가지 네이밍 컨벤션과 데이터 매핑 전략&quot; data-og-description=&quot;개발자로 일하다 보면 프론트엔드 개발자나 인프라 담당자와 협업할 때 이런 상황을 자주 겪는다.프론트엔드에서 API 로 user_name 이라는 데이터를 보냈는데, 백엔드의 자바 객체(DTO)에서는 userName&quot; data-og-host=&quot;hsunnystory.tistory.com&quot; data-og-source-url=&quot;https://hsunnystory.tistory.com/258&quot; data-og-url=&quot;https://hsunnystory.tistory.com/258&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/NLGLL/dJMb8WeAzAR/KjVRdEMKJGydKVrOXYTfRK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/jrtVn/dJMb8YXMs56/McAOeVltFsm6SwE6FJ0tR1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://hsunnystory.tistory.com/258&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hsunnystory.tistory.com/258&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/NLGLL/dJMb8WeAzAR/KjVRdEMKJGydKVrOXYTfRK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/jrtVn/dJMb8YXMs56/McAOeVltFsm6SwE6FJ0tR1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Clean Code] 실무에서 알아야할 5가지 네이밍 컨벤션과 데이터 매핑 전략&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;개발자로 일하다 보면 프론트엔드 개발자나 인프라 담당자와 협업할 때 이런 상황을 자주 겪는다.프론트엔드에서 API 로 user_name 이라는 데이터를 보냈는데, 백엔드의 자바 객체(DTO)에서는 userName&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hsunnystory.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 스프링 부트 환경에서 이질적인 네이밍 컨벤션들을 어떻게 유연하게 연결해 주는지, 그 핵심 원리인 &lt;b&gt;Relaxed Binding&lt;/b&gt;에 대해 알아본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 부트 애플리케이션을 개발하고 배포하다 보면 잉런 경험이 한 번쯤 있을 것이다. 로컬 환경에서는 application.yml에 max-reetry-count=3 이라고 표기하여 개발을 진행한다. 그런데 Docker나 Kubernetes를 통해 운영 서버에 배포할 때, 인프라 담당자는 OS 환경 변수로 MAX_RETRY_COUNT=5를 주입한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 코드에서는 maxRetryCount 라는 카멜 케이스(Camel Case) 변수를 사용하고, 설정 파일은 케밥 케이스(Kebab Case)를 쓰며, OS 환경 변수는 대문자 스네이크 케이스(Upper Snake Case)를 쓴다. &lt;b&gt;놀랍게도 스프링 부트는 아무런 추가 설정 없이 이 모든 것을 알아서 하나의 자바 변수에 매핑해 준다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Relaxed Binding이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Relaxed Binding은 단어 뜻 그대로 느슨한 바인딩을 의미한다. 스프링 부트 환경에서 application.yml, application.properties, OS 환경 변수 등 다양한 외부 설정값들을 자바 객체(Bean)의 필드에 매핑할 때, &lt;b&gt;이름의 규칙이 정확히 일치하지 않아도 문맥상 같은 의미라면 자동으로 매핑&lt;/b&gt;해 주는 기능이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;매핑 지원 규칙 예시&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 객체의 필드명이 firstName 일 때, 아래의 모든 표기법이 동일하게 매핑된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- Kebab Case : first-name (.yml이나 .properties 에서 권장)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- Camel Case : firstName&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- Snake Case : first_name&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- Upper Snake Case : FIRST_NAME (OS 환경 변수에서 주로 사용)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. @Value의 한계와 파편화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자들이 가장 많이 하는 실수는 모든 설정값을 @Value로 주입 받는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Bad Code: 파편화된 설정과 @Value 남용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Service &lt;br /&gt;public&amp;nbsp;class&amp;nbsp;PaymentService&amp;nbsp;{ &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;문제&amp;nbsp;1:&amp;nbsp;프로퍼티&amp;nbsp;키값이&amp;nbsp;오타가&amp;nbsp;나도&amp;nbsp;컴파일&amp;nbsp;타임에&amp;nbsp;잡히지&amp;nbsp;않는다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;문제&amp;nbsp;2:&amp;nbsp;곳곳에&amp;nbsp;@Value가&amp;nbsp;흩어져&amp;nbsp;있어&amp;nbsp;설정의&amp;nbsp;응집도가&amp;nbsp;떨어진다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Value(&quot;${app.payment.max-retry-count}&quot;) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;int&amp;nbsp;maxRetryCount; &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Value(&quot;${app.payment.api-key}&quot;) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;String&amp;nbsp;apiKey; &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;process()&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&quot;Retry&amp;nbsp;count:&amp;nbsp;&quot;&amp;nbsp;+&amp;nbsp;maxRetryCount); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;결제&amp;nbsp;로직... &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;문제점&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Value는 스프링의 Relaxed Binging을 완벽하게 지원하지 않는다. (일부 제한적으로 동작하지만, 환경 변수 변환 등에서 제약이 많다.) 또한, 여러 서비스에서 동일한 설정값을 사용할 경우 코드가 중복되며, 객체지향의 &lt;b&gt;응집도&lt;/b&gt; 측면에서도 설정 정보가 비즈니스 로직에 결합하여 매우 나쁜 구조가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 해결책: @ConfigurationProperties와 Relaxed Binding&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 부트는 외부 설정을 강력한 타입 세이프(Type-safe) 구조로 관리할 수 있도록 @ConfigurationProperties를 제공한다. 이 어노테이션은 &lt;b&gt;Relaxed Binding을 100% 지원&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Good Code: 객체지향적인 설정 관리와 유연한 매핑&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정값을 담을 전용 클래스를 생성한다. 자바 16이상이라면 record를 활용하여 불변 객체로 아주 깔끔하게 작성할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;import&amp;nbsp;org.springframework.boot.context.properties.ConfigurationProperties; &lt;br /&gt;&lt;br /&gt;//&amp;nbsp;설정&amp;nbsp;정보를&amp;nbsp;독립적인&amp;nbsp;도메인으로&amp;nbsp;분리하여&amp;nbsp;응집도를&amp;nbsp;높인다. &lt;br /&gt;//&amp;nbsp;prefix는&amp;nbsp;반드시&amp;nbsp;소문자&amp;nbsp;케밥&amp;nbsp;케이스(kebab-case)로&amp;nbsp;작성해야&amp;nbsp;한다. &lt;br /&gt;@ConfigurationProperties(prefix&amp;nbsp;=&amp;nbsp;&quot;app.payment&quot;) &lt;br /&gt;public&amp;nbsp;record&amp;nbsp;PaymentProperties( &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;int&amp;nbsp;maxRetryCount,&amp;nbsp;//&amp;nbsp;환경&amp;nbsp;변수&amp;nbsp;APP_PAYMENT_MAX_RETRY_COUNT&amp;nbsp;와&amp;nbsp;자동&amp;nbsp;매핑 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;apiKey&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;환경&amp;nbsp;변수&amp;nbsp;APP_PAYMENT_API_KEY&amp;nbsp;와&amp;nbsp;자동&amp;nbsp;매핑 &lt;br /&gt;)&amp;nbsp;{ &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 비즈니스 로직에서는 이 PaymentProperties를 의존성 주입 받아서 사용하기만 하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Service &lt;br /&gt;@RequiredArgsConstructor &lt;br /&gt;public&amp;nbsp;class&amp;nbsp;PaymentService&amp;nbsp;{ &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;설정&amp;nbsp;정보가&amp;nbsp;캡슐화된&amp;nbsp;객체&amp;nbsp;자체를&amp;nbsp;주입받음 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;PaymentProperties&amp;nbsp;paymentProperties; &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;process()&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;비즈니스&amp;nbsp;로직은&amp;nbsp;설정값이&amp;nbsp;어떻게&amp;nbsp;주입되었는지(YAML인지,&amp;nbsp;OS환경변수인지)&amp;nbsp;알&amp;nbsp;필요가&amp;nbsp;없다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;int&amp;nbsp;retryCount&amp;nbsp;=&amp;nbsp;paymentProperties.maxRetryCount(); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&quot;Retry&amp;nbsp;count:&amp;nbsp;&quot;&amp;nbsp;+&amp;nbsp;retryCount); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;아키텍처적 이점&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인프라 환경(Docker, OS)의 명명 규칙과 애플리케이션의 명명 규칙(Camel Case) 사이의 &lt;b&gt;임피던스 불일치(Impedance Mismatch)를 스프링 부트가 오나벽히 해결&lt;/b&gt;해 준다. OCP(개방-폐쇄 원칙) 관점에서도 인프라 환경이 바뀌어 프로퍼티 주입 방식이 달라져도 자바 코드는 단 한줄도 수정할 필요가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 주의사항&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Prefix는 반드시 Kebab Case로 작성할것&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- @ConfigurationProperties(prefix = &quot;app.payment&quot;)&amp;nbsp; (O)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- @ConfigurationProperties(prefix = &quot;app.paymentConfig&quot;)&amp;nbsp; (X)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;-&amp;gt; 스프링 부트 규약상 prefix는 영문 소문자와 대시(-) 만 허용된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@Value 대신 @ConfigurationProperties를 기본으로 사용할 것&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 단일 값을 주입받는 극히 예외적인 상황이 아니라면, 설정은 그룹화하여 객체로 다루는 것이 유지보수와 테스트(Mocking)에 훨씬 유리하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;환경 변수(OS Env) 적용 규칙 기억하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- OS 환경 변수는 점(.)을 사용할 수 없으므로, 언더스코어(_)로 치환하여 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 예 : app.payment.max-retry-count -&amp;gt; APP_PAYMENT_MAX_RETRY_COUNT&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 부트의 &lt;b&gt;Relaxed Binding&lt;/b&gt;은 단순한 편의 기능을 넘어, &lt;b&gt;Infrastructure의 제약과 객체지향 설계 사이의 간극을 메워주는 핵심 아키텍처 요소&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 이상 @Value를 이곳저곳 흩뿌려놓지 말고, @ConfigruationProperties를 통해 설정을 도메인 객체로 격상시키자.&amp;nbsp;&lt;/p&gt;</description>
      <category>Web Programming/Spring</category>
      <category>ConfigurationProperties</category>
      <category>Java</category>
      <category>RelaxedBinding</category>
      <category>Spring</category>
      <category>SpringBoot</category>
      <category>스프링</category>
      <category>스프링부트</category>
      <category>자바</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/259</guid>
      <comments>https://hsunnystory.tistory.com/259#entry259comment</comments>
      <pubDate>Thu, 9 Apr 2026 14:06:33 +0900</pubDate>
    </item>
    <item>
      <title>[Clean Code] 실무에서 알아야할 5가지 네이밍 컨벤션과 데이터 매핑 전략</title>
      <link>https://hsunnystory.tistory.com/258</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이전 포스팅에서 스프링 부트의 Relaxed Binding에 대해 알아보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://hsunnystory.tistory.com/259&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://hsunnystory.tistory.com/259&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1775711251238&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Spring Boot] Relaxed Binding란&quot; data-og-description=&quot;이전 포스팅에서 5가지 네이밍 컨벤션과 데이터 매핑 전략에 대해 알아보았다.https://hsunnystory.tistory.com/258 [Clean Code] 실무에서 알아야할 5가지 네이밍 컨벤션과 데이터 매핑 전략개발자로 일하다 &quot; data-og-host=&quot;hsunnystory.tistory.com&quot; data-og-source-url=&quot;https://hsunnystory.tistory.com/259&quot; data-og-url=&quot;https://hsunnystory.tistory.com/259&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/coshDo/dJMb8Z3sevy/RhTfARdBOt3LopS4ywk88k/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/dv8oqu/dJMb8YXMtes/UR0pZJoC0cax317GlkG6p0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://hsunnystory.tistory.com/259&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hsunnystory.tistory.com/259&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/coshDo/dJMb8Z3sevy/RhTfARdBOt3LopS4ywk88k/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/dv8oqu/dJMb8YXMtes/UR0pZJoC0cax317GlkG6p0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Spring Boot] Relaxed Binding란&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이전 포스팅에서 5가지 네이밍 컨벤션과 데이터 매핑 전략에 대해 알아보았다.https://hsunnystory.tistory.com/258 [Clean Code] 실무에서 알아야할 5가지 네이밍 컨벤션과 데이터 매핑 전략개발자로 일하다&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hsunnystory.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이번 포스팅에서는 자주 쓰이는 5가지 네이밍 케이스를 정리하고, 특히 각기 다른 컨벤션을 가진 시스템(DB, JSON API, OS)간의 임피던스 불일치(Impedance Mismatch)를 아키텍처 관점에서 어떻게 해결해야 하는지 알아본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자로 일하다 보면 프론트엔드 개발자나 인프라 담당자와 협업할 때 이런 상황을 자주 겪는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드에서 API 로 user_name 이라는 데이터를 보냈는데, 백엔드의 자바 객체(DTO)에서는 userName으로 받고 있어 값이 전부 null로 매핑되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 자바 변수명을 user_name으로 바꾸면 해결되겠지라고 생각한다면 큰 오산이다. 프로그래밍에서 네이밍 컨벤션(Nameing Convension)은 단순한 취향 차이가 아니라, &lt;b&gt;시스템 간의 데이터를 주고받는 규악이자 언어 생태계의 표준&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 5가지 주요 네이밍 컨벤션&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;카멜 (Camel, camelCase)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 첫 단어는 소문자, 이어지는 단어의 첫 글자는 대문자로 표기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- Java, JavaScript의 변수명 및 메서드명, JSON의 표준 키값에 사용&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;파스칼 (Pascal, PascalCase)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 카멜 케이스와 같지만, 첫 단어의 첫 글자도 대문자로 표기한다.(Upper Camel Case)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- Java, C# 등의 클래스명, 인터페이스명에 사용&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;케밥 (Kebab, kebab-case)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 모든 단어를 소문자로 쓰고, 단어 사이를 하이픈( - )으로 연결&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- URL URI 경로, HTML/CSS의 클래스명, application.yml 등의 설정 파일 키값에 사용&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;스네이크 (Snacke, snake_case)&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 모든 단어를 소문자로 쓰고, 단어 사이를 언더스코어( _ )로 연결&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- Python의 변수명, RDB의 테이블 및 컬럼명에 사용&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;어퍼 스네이크 (Upper Snake, UPPER_SNAKE_CASE)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 스네이크 케이스와 같지만, 모든 글자를 대문자로 표기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- Java의 static final 상수, OS 환경 변수(Environment Variables)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;스네이크와 어퍼 스네이크를 분리하는 이유&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;형태상으로는 대소문자 차이일 뿐이지만, 실무에서는 이 둘을 완전히 다른 목적으로 취급한다. snake_case는 DB나 API를 통해 흘러 다니는 &lt;b&gt;가변적인 일반 데이터(State)&lt;/b&gt;를 의미하고, UPPER_SNAKE_CASE는 코드 내부의 상수나 OS 인프라에서 주입되는 &lt;b&gt;불변의 설정(Constant)&lt;/b&gt;을 의미한다. 코드는 의도를 담아야 하므로 두 케이스는 아키텍처 상 분리해서 사고하는 것이 옳다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 문제점 - 컨벤션이 충돌하는 경계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 큰 문제는 서로 다른 컨벤션을 사용하는 시스템이 통신할 때 발생한다. 데이터베이스는 주로 스네이크 케이스(user_id)를 사용하고, 자바 백엔드는 카멜 케이스(userId)를 사용하며, 외부 API는 종종 스네이크 케이스 포멧의 JSON을 던져준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 데이터를 제대로 받기 위해 자바 코드의 네이밍 규칙을 무너뜨리는 것은 객체지향 설계의 본질을 해치는 최악의 접근이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Bad Code: 생태계 표준을 무시한 DTO 설계&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public class UserRequestDto { &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;문제&amp;nbsp;1:&amp;nbsp;Java의&amp;nbsp;카멜&amp;nbsp;케이스&amp;nbsp;표준을&amp;nbsp;위반했다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;문제&amp;nbsp;2:&amp;nbsp;외부&amp;nbsp;시스템의&amp;nbsp;JSON&amp;nbsp;키&amp;nbsp;포맷이&amp;nbsp;변경되면&amp;nbsp;자바&amp;nbsp;코드(필드명,&amp;nbsp;getter/setter)를&amp;nbsp;전부&amp;nbsp;뜯어고쳐야&amp;nbsp;한다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;String&amp;nbsp;user_name; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;String&amp;nbsp;phone_number; &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;String&amp;nbsp;getUser_name()&amp;nbsp;{&amp;nbsp;return&amp;nbsp;user_name;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;String&amp;nbsp;getPhone_number()&amp;nbsp;{&amp;nbsp;return&amp;nbsp;phone_number;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 코드는 자바의 표준을 깨뜨릴 뿐만 아니라, 외부 통신 규약과 내부 도메인 로직을 강하게 결합시킨다. &lt;b&gt;OCP(개방-폐쇄 원칙)에 위배&lt;/b&gt;되는 전형적인 사례다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 해결책 - 어댑터(Adapter) 역할을 하는 프레임워크 기능 활용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설계의 원칙은 명확하다. &lt;b&gt;내부(Java)는 철저히 자바의 표준(Camel Case)을 따르고, 시스템의 경계(Controller, Repository)에서만 직렬화/역직렬화를 통해 포맷을 변환한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot 환경에서는 Jackson 라이브러리와 Hibernate가 이 어댑터 역할을 아주 훌륭하게 수행해 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Good Code: Jackson을 활용한 JSON 매핑&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;개별 필드 매핑 ( @JsonProperty )&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 필드만 다른 이름으로 매핑해야 할 때 유용하다. 자바 필드명은 userName을 유지하면서, JSON으로 들어오고 나갈 때는 user_name으로 안전하게 변환된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public class UserRequestDto {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@JsonProperty(&quot;user_name&quot;) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;String&amp;nbsp;userName;&amp;nbsp;//&amp;nbsp;Java&amp;nbsp;표준인&amp;nbsp;Camel&amp;nbsp;Case&amp;nbsp;유지 &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@JsonProperty(&quot;phone_number&quot;) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;String&amp;nbsp;phoneNumber; &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;클래스 단위 일괄 매핑 ( @JsonNameing )&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 외부 API가 100% 스네이크 케이스 포맷을 사용한다면, 일일이 @JsonProperty를 붙이는 것은 비효율적이다. 이때는 클래스 레벨에서 Naming Strategy를 지정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;import&amp;nbsp;com.fasterxml.jackson.databind.PropertyNamingStrategies; &lt;br /&gt;import&amp;nbsp;com.fasterxml.jackson.databind.annotation.JsonNaming; &lt;br /&gt;&lt;br /&gt;//&amp;nbsp;이&amp;nbsp;DTO의&amp;nbsp;모든&amp;nbsp;필드는&amp;nbsp;직렬화/역직렬화&amp;nbsp;시&amp;nbsp;자동으로&amp;nbsp;스네이크&amp;nbsp;케이스와&amp;nbsp;매핑된다. &lt;br /&gt;@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) &lt;br /&gt;public&amp;nbsp;class&amp;nbsp;UserRequestDto&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;String&amp;nbsp;userName;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;JSON:&amp;nbsp;user_name &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;String&amp;nbsp;phoneNumber;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;JSON:&amp;nbsp;phone_number &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;String&amp;nbsp;emailAddress;&amp;nbsp;&amp;nbsp;//&amp;nbsp;JSON:&amp;nbsp;email_address &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스와의 매핑도 마찬가지다. Spring Data JPA를 사용하면 SpringPhysialNamingStrategy가 기본적용되어, 자바 엔티티의 userName 필드를 DB의 user_name 컬럼으로 자동 변환해 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카멜, 파스칼, 케밥, 스네이크, 어퍼 스네이크 케이스는 단순한 텍스트 형태가 아니라 각 기술 생태계가 합의한 언어다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프레임워크가 제공하는 매핑 전략(Jackson, JPA Naming Strategy 등)을 시스템의 경계에 배치하여 통신을 매끄럽게 번역하자. 네이밍 컨벤션을 지키는 것은 클린 코드와 유연한 아키텍처를 향한 가장 기본적이고 중요한 첫걸음이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Software Engineering</category>
      <category>cleancode</category>
      <category>Jackson</category>
      <category>Naming Convension</category>
      <category>네이밍 컨벤션</category>
      <category>스네이크 케이스</category>
      <category>카멜 케이스</category>
      <category>케밥 케이스</category>
      <category>파스칼 케이스</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/258</guid>
      <comments>https://hsunnystory.tistory.com/258#entry258comment</comments>
      <pubDate>Tue, 7 Apr 2026 14:57:16 +0900</pubDate>
    </item>
    <item>
      <title>[Software Engineering] 테스트 주도 개발(TDD)란?</title>
      <link>https://hsunnystory.tistory.com/257</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;개발자라면 누구나 한 번쯤 TDD(Test-Driven Development) 테스트 주도 개발에 대해 들어보았을 것이다. 이번 포스팅에서는 TDD에 대해서 다룬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 문제 상황 : 테스트 코드를 나중에 작성하면 생기는 일&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통의 개발 흐름은 요구사항을 받으면 일단 비즈니스 로직부터 작성한다. 기능이 정상 동작하는 것을 확인한 후 뒤늦게 테스트 코드를 덮어 씌운다 (이를 Test-Last 방식이라 한다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Bad Code: 테스트하기 어려운 설계&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주말에는 영화 티켓값을 10% 할인해 주는 로직을 개발했다고 가정하자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public&amp;nbsp;class&amp;nbsp;TicketService&amp;nbsp;{ &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;[Bad&amp;nbsp;Code]&amp;nbsp;운영&amp;nbsp;코드를&amp;nbsp;먼저&amp;nbsp;짜면,&amp;nbsp;현재&amp;nbsp;시간에&amp;nbsp;강하게&amp;nbsp;결합된&amp;nbsp;코드가&amp;nbsp;나온다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;int&amp;nbsp;calculatePrice(int&amp;nbsp;basePrice)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;DayOfWeek&amp;nbsp;today&amp;nbsp;=&amp;nbsp;LocalDate.now().getDayOfWeek();&amp;nbsp;//&amp;nbsp;제어할&amp;nbsp;수&amp;nbsp;없는&amp;nbsp;외부&amp;nbsp;상태(현재&amp;nbsp;시간)에&amp;nbsp;의존 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(today&amp;nbsp;==&amp;nbsp;DayOfWeek.SATURDAY&amp;nbsp;||&amp;nbsp;today&amp;nbsp;==&amp;nbsp;DayOfWeek.SUNDAY)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;(int)&amp;nbsp;(basePrice&amp;nbsp;*&amp;nbsp;0.9); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;basePrice; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 코드를 검증하기 위해 테스트 코드를 작성한다고 했을 때, 이 테스트는 &lt;b&gt;오늘이 무슨 요일이냐에 따라 성공과 실패가 오락가락 하는 엉터리 테스트&lt;/b&gt;가 된다. 수요일에 이 코드를 커밋하고 CI/CD를 돌리면 주말 할인 테스트는 무조건 실패한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. TDD란? (Red-Green-Refactor)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TDD는 개발의 순서를 완전히 뒤집는다. 실패하는 테스트 코드를 먼저 작성하고(Red), 그 테스트를 통과시킬 만큼만 코드를 구현한 뒤(Green), 코드를 깔끔하게 다듬는(Refactor) 과정을 짧게 반복한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;RED (실패하는 테스트)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 구현되지 않은 기능의 테스트를 작성한다. 이때 어떻게 구현할지가 아니라 &lt;b&gt;무엇을&lt;/b&gt; 해야 하는지(인터페이스와 스팩)를 먼저 고민한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;GREEN (테스트 통과)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꼼수를 써서라도 가장 빠르게 테스트를 통과 시킨다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;REFACTOR(리팩토링)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트가 코드를 보호해주고 있으므로, 안심하고 중복을 제거하고 객체 지향적인 설계로 다듬는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 실전 TDD 적용&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Step 1: RED (테스트를 먼저 설계한다.)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;할인 로직을 검증하려면, 내가 요일을 임의로 조작할 수 있어야 한다는 사실을 &lt;b&gt;테스트 코드를 짜면서 자연스럽게 깨닫게 된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;class&amp;nbsp;TicketServiceTest&amp;nbsp;{ &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Test &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@DisplayName(&quot;주말(토,&amp;nbsp;일)에는&amp;nbsp;티켓&amp;nbsp;가격이&amp;nbsp;10%&amp;nbsp;할인되어야&amp;nbsp;한다.&quot;) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;void&amp;nbsp;weekend_discount()&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;TicketService&amp;nbsp;service&amp;nbsp;=&amp;nbsp;new&amp;nbsp;TicketService(); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;given:&amp;nbsp;내가&amp;nbsp;직접&amp;nbsp;날짜를&amp;nbsp;주입(제어)할&amp;nbsp;수&amp;nbsp;있도록&amp;nbsp;인터페이스를&amp;nbsp;설계하게&amp;nbsp;됨! &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;LocalDate&amp;nbsp;saturday&amp;nbsp;=&amp;nbsp;LocalDate.of(2023,&amp;nbsp;10,&amp;nbsp;28);&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;when &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;int&amp;nbsp;price&amp;nbsp;=&amp;nbsp;service.calculatePrice(10000,&amp;nbsp;saturday); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;then &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;assertThat(price).isEqualTo(9000); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;코드 구현이 없으므로 당연히 컴파일 에러가 나거나 테스트에 실패한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Step 2: GREEN (가장 단순한 구현)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를 통과시키는 것이 최우선이다. 오직 테스트를 만족시키는 코드만 작성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public&amp;nbsp;class&amp;nbsp;TicketService&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;테스트&amp;nbsp;코드를&amp;nbsp;통해&amp;nbsp;'날짜'를&amp;nbsp;외부에서&amp;nbsp;주입받아야&amp;nbsp;한다는&amp;nbsp;설계를&amp;nbsp;이끌어냈다! &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;int&amp;nbsp;calculatePrice(int&amp;nbsp;basePrice,&amp;nbsp;LocalDate&amp;nbsp;date)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(date.getDayOfWeek()&amp;nbsp;==&amp;nbsp;DayOfWeek.SATURDAY&amp;nbsp;||&amp;nbsp;date.getDayOfWeek()&amp;nbsp;==&amp;nbsp;DayOfWeek.SUNDAY)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;(int)&amp;nbsp;(basePrice&amp;nbsp;*&amp;nbsp;0.9); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;basePrice; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Step 3: REFACTOR (안전한 개선)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 테스트가 든든하게 받쳐주고 있으니, 로직을 더 우아하게 개선해 본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public&amp;nbsp;class&amp;nbsp;TicketService&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;int&amp;nbsp;calculatePrice(int&amp;nbsp;basePrice,&amp;nbsp;LocalDate&amp;nbsp;date)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(isWeekend(date))&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;(int)&amp;nbsp;(basePrice&amp;nbsp;*&amp;nbsp;0.9); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;basePrice; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;주말&amp;nbsp;여부를&amp;nbsp;판단하는&amp;nbsp;로직을&amp;nbsp;별도&amp;nbsp;메서드로&amp;nbsp;추출&amp;nbsp;(가독성&amp;nbsp;향상) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;boolean&amp;nbsp;isWeekend(LocalDate&amp;nbsp;date)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;DayOfWeek&amp;nbsp;day&amp;nbsp;=&amp;nbsp;date.getDayOfWeek(); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;day&amp;nbsp;==&amp;nbsp;DayOfWeek.SATURDAY&amp;nbsp;||&amp;nbsp;day&amp;nbsp;==&amp;nbsp;DayOfWeek.SUNDAY; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. TDD의 가치: 테스트가 아닌 설계(Design)을 주도한다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TDD의 목적은 단순히 커버리지 100%의 방대한 테스트 코드를 짜는 것이 아니다. 앞선 예제에서 보았듯, &lt;b&gt;사용자(클라이언트)의 관점에서 테스트를 먼저 작성하다 보면, 필연적으로 테스트하기 좋은 코드(= 의존성이 낮고 모듈화가 잘 된 결합도 낮은 코드)를 강제로 설계&lt;/b&gt;하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- LocalDate.now() 같은 숨겨진 의존성은 메서드 파라미터나 의존성 주입(DI)으로 끌어내게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 비대한 Service 클래스는 테스트가 너무 복잡해지기 떄문에, 자연스럽게 역할을 분리(Entity 나 다른객체로 위임)하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;바빠서 테스트 코드를 짤 시간이 없다는 말은 사실 모순이다. &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 이후에 손으로 Postman을 수십 번 클릭하며 디버깅하는 시간, 라이브 서버에서 터진 버그를 수습하느라 날리는 주말을 생각하면 &lt;b&gt;TDD는 시간을 잡아먹는 것이 아니라 오히려 시간을 가장 확실하게 아껴주는 투자&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;복잡한 비즈니스 로직이나 도메인 핵심 규칙만큼은 반드시 테스트를 먼저 작성하는 TDD 사이클을 경험하는 습관을 들이자&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Software Engineering</category>
      <category>agile</category>
      <category>JUnit</category>
      <category>Refacoring</category>
      <category>TDD</category>
      <category>에자일</category>
      <category>테스트주도개발</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/257</guid>
      <comments>https://hsunnystory.tistory.com/257#entry257comment</comments>
      <pubDate>Mon, 6 Apr 2026 17:40:42 +0900</pubDate>
    </item>
    <item>
      <title>[Software Engineering] 트랜잭션 스크립트 패턴(Transaction Script Pattern)</title>
      <link>https://hsunnystory.tistory.com/256</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;최근 몇 년간 백엔드 생태계에서는 &lt;b&gt;도메인 주도 설계(DDD)&lt;/b&gt;와 &lt;b&gt;객체지향&lt;/b&gt;이 최고의 미덕처럼 여겨지고 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://hsunnystory.tistory.com/255&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://hsunnystory.tistory.com/255&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1774943911235&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Skill] DDD(도메인 주도 설계)란&quot; data-og-description=&quot;실무를 하다 보면, 수천 줄이 넘어가는 거대한 Service 클래스를 마주하게 되는 날이 온다. 비즈니스 로직은 모두 서비스 계층에 몰려있고, 정작 데이터의 주체여야 할 Entity 클래스들은 그저 getter&quot; data-og-host=&quot;hsunnystory.tistory.com&quot; data-og-source-url=&quot;https://hsunnystory.tistory.com/255&quot; data-og-url=&quot;https://hsunnystory.tistory.com/255&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ceO4Vo/dJMb86nXH1I/e5HzqF9P663cZ2XxoexiKK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/wmVop/dJMb8WezMw3/hqQ10h5vEYpcnVLYPek9u1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://hsunnystory.tistory.com/255&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hsunnystory.tistory.com/255&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ceO4Vo/dJMb86nXH1I/e5HzqF9P663cZ2XxoexiKK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/wmVop/dJMb8WezMw3/hqQ10h5vEYpcnVLYPek9u1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Skill] DDD(도메인 주도 설계)란&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;실무를 하다 보면, 수천 줄이 넘어가는 거대한 Service 클래스를 마주하게 되는 날이 온다. 비즈니스 로직은 모두 서비스 계층에 몰려있고, 정작 데이터의 주체여야 할 Entity 클래스들은 그저 getter&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hsunnystory.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션 스크립트 패턴(Transaction Script Pattern)은 무조건 피해야 할 레거시(Legacy)이자 안티 패턴일까? 이번 포스팅에서는 트랜잭션 스크립트 패턴에 대해 알아본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 트랜잭션 스크립트 패턴(Transaction Script Pattern) 이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션 스크립트는 &lt;b&gt;사용자의 요청(Request) 하나당 하나의 비즈니스 트랜잭션(스크립트)을 매핑하여 처리하는 방식&lt;/b&gt;이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;동작방식&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨트롤러에서 요청이 들어오면, Service 계층의 메서드 하나가 시작부터 끝까지 모든 흐름을 제어한다. DB에서 데이터를 읽어오고, 계산하고, 상태를 변경한 뒤 다시 DB에 저장하는 일련의 절차(Procedure)를 순서대로 작성한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;특징&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 지향이라기보다는 &lt;b&gt;절차 지향(Procedural)&lt;/b&gt; 프로그래밍에 가깝다. 데이터베이스의 테이블과 1:1로 매핑되는 단순한 데이터 구조(DTO 또는 빈약한 Entity)를 다룬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 왜 실무에서 가장 많이 쓰일까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션 스크립트의 가장 큰 장점은 &lt;b&gt;압도적인 직관성&lt;/b&gt;과 &lt;b&gt;빠른 개발 속도&lt;/b&gt;다. 복잡한 도메인 지식이 없는 단순한 CRUD 중심의 애플리케이션에서는 이보다 좋은 패턴이 없다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;호텔 객실을 예약 하는 단순한 API&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Good Code&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Service &lt;br /&gt;@RequiredArgsConstructor &lt;br /&gt;public&amp;nbsp;class&amp;nbsp;HotelBookingService&amp;nbsp;{ &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;RoomRepository&amp;nbsp;roomRepository; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;BookingRepository&amp;nbsp;bookingRepository; &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;[Good&amp;nbsp;Code]&amp;nbsp;단순한&amp;nbsp;요구사항에서는&amp;nbsp;가장&amp;nbsp;직관적이고&amp;nbsp;효율적이다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Transactional &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;bookRoom(Long&amp;nbsp;roomId,&amp;nbsp;Long&amp;nbsp;userId)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;1.&amp;nbsp;데이터&amp;nbsp;조회 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Room&amp;nbsp;room&amp;nbsp;=&amp;nbsp;roomRepository.findById(roomId).orElseThrow(); &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;2.&amp;nbsp;비즈니스&amp;nbsp;로직&amp;nbsp;(검증&amp;nbsp;및&amp;nbsp;계산) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(!room.isAvailable())&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw&amp;nbsp;new&amp;nbsp;IllegalStateException(&quot;이미&amp;nbsp;예약된&amp;nbsp;객실입니다.&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;int&amp;nbsp;price&amp;nbsp;=&amp;nbsp;room.getBasePrice(); &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;3.&amp;nbsp;데이터&amp;nbsp;저장&amp;nbsp;(상태&amp;nbsp;변경) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;room.setAvailable(false);&amp;nbsp;//&amp;nbsp;엔티티는&amp;nbsp;그저&amp;nbsp;데이터를&amp;nbsp;담는&amp;nbsp;역할만&amp;nbsp;수행 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Booking&amp;nbsp;booking&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Booking(roomId,&amp;nbsp;userId,&amp;nbsp;price); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bookingRepository.save(booking); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;roomRepository.save(room); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점&lt;/b&gt; : 코드를 위에서 아래로 읽어 내려가기만 하면 비즈니스 흐름이 완벽하게 이해된다. 객체 간의 복잡한 연관관계나 메시지 전달을 고민할 필요가 없어 학습 곡선이 매우 낮다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 지옥이 되는 경우&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 패턴의 치명적인 단점은 요구사항이 복잡해질수로 서비스 계층이 통제 불능의 &lt;b&gt;God Class로 비대해진다&lt;/b&gt;는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스가 성공하여 예약 로직에 &quot;VIP 회원 할인&quot;, &quot;주말 할증&quot;, &quot;연속 숙박 시 쿠폰 적용&quot; 같은 복잡한 도메인 규칙이 추가된다고 가정해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Bad Code&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Service &lt;br /&gt;public&amp;nbsp;class&amp;nbsp;HotelBookingService&amp;nbsp;{ &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Transactional &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;bookRoom(Long&amp;nbsp;roomId,&amp;nbsp;Long&amp;nbsp;userId,&amp;nbsp;String&amp;nbsp;couponCode,&amp;nbsp;boolean&amp;nbsp;isWeekend)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Room&amp;nbsp;room&amp;nbsp;=&amp;nbsp;roomRepository.findById(roomId).orElseThrow(); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;User&amp;nbsp;user&amp;nbsp;=&amp;nbsp;userRepository.findById(userId).orElseThrow(); &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(!room.isAvailable())&amp;nbsp;{&amp;nbsp;...&amp;nbsp;} &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;[Bad&amp;nbsp;Code]&amp;nbsp;요구사항이&amp;nbsp;늘어날&amp;nbsp;때마다&amp;nbsp;if-else&amp;nbsp;분기가&amp;nbsp;끊임없이&amp;nbsp;추가된다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;int&amp;nbsp;price&amp;nbsp;=&amp;nbsp;room.getBasePrice(); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(isWeekend)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;price&amp;nbsp;+=&amp;nbsp;50000;&amp;nbsp;//&amp;nbsp;주말&amp;nbsp;할증 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(user.getGrade()&amp;nbsp;==&amp;nbsp;Grade.VIP)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;price&amp;nbsp;=&amp;nbsp;(int)&amp;nbsp;(price&amp;nbsp;*&amp;nbsp;0.8);&amp;nbsp;//&amp;nbsp;VIP&amp;nbsp;할인 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(couponCode&amp;nbsp;!=&amp;nbsp;null)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Coupon&amp;nbsp;coupon&amp;nbsp;=&amp;nbsp;couponRepository.findByCode(couponCode); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(coupon.isValid())&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;price&amp;nbsp;-=&amp;nbsp;coupon.getDiscountAmount();&amp;nbsp;//&amp;nbsp;쿠폰&amp;nbsp;적용 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;coupon.setUsed(true); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;...&amp;nbsp;코드가&amp;nbsp;수백&amp;nbsp;줄로&amp;nbsp;길어지며&amp;nbsp;유지보수가&amp;nbsp;불가능해진다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;room.setAvailable(false); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bookingRepository.save(new&amp;nbsp;Booking(roomId,&amp;nbsp;userId,&amp;nbsp;price)); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제점 1 (응집도 저하)&lt;/b&gt; : 가격을 계산하는 중요한 비즈니스 규칙이 Service 계층에 하드코딩 되어 있다. 만약 결제 서비스(PaymentService) 에서도 가격 계산 로직이 필요하다면 똑같은 코드를 복사 붙여넣기 해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제점 2 (테스트의 어려움)&lt;/b&gt; : 이 로직 하나를 테스트하기 위해 DB를 연결하거나 수많은 Repository를 Mocking 해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;트랜잭션 스크립트를 선택하는 경우&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 비즈니스 로직이 단순한 CRUD 수준일 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 빠른 시장 진입(MVP 개발)이 최우선 목표일 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 개발팀의 객체 지향 및 DDD 숙련도가 낮을 때&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;도메인 모델(DDD)을 선택하는 경우&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 비즈니스 규칙이 복잡하고, 상태 변화가 다양한 도메인일 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 동일한 로직이 여러 서비스에서 중복해서 사용되기 시작할 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자라면 명목적으로 최신 트렌드를 쫓기보다는, &lt;b&gt;현재 우리 시스템의 복잡도가 어느 수준인지 파악하고 그에 맞는 적절한 패턴을 취사선택&lt;/b&gt;하는 안목을 길러야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Software Engineering</category>
      <category>architecture</category>
      <category>DDD</category>
      <category>Spring</category>
      <category>TransactionScript</category>
      <category>객체지향</category>
      <category>아키텍쳐</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/256</guid>
      <comments>https://hsunnystory.tistory.com/256#entry256comment</comments>
      <pubDate>Tue, 31 Mar 2026 17:09:38 +0900</pubDate>
    </item>
    <item>
      <title>[Software Engineering] DDD(도메인 주도 설계)란</title>
      <link>https://hsunnystory.tistory.com/255</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;실무를 하다 보면, 수천 줄이 넘어가는 거대한 Service 클래스를 마주하게 되는 날이 온다. 비즈니스 로직은 모두 서비스 계층에 몰려있고, 정작 데이터의 주체여야 할 Entity 클래스들은 그저 getter와 setter만 덩그러니 가지고 있는 데이터 덩어리로 전락해 있다. 이를 빈약한 도메인 모델(Anemic Domain Model)이라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템이 복잡해질수록 이런 구조는 유지보수를 지옥으로 이끈다. 이번 포스팅에서는 비즈니스 로직을 올바른 위치에 배치하고, 소프트웨어가 현실 세계의 비즈니스를 정확히 투영하도록 돕는 &lt;b&gt;도메인 주도 설계(Domain Driven Design, DDD)&lt;/b&gt;에 대해 알아본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 문제상황 : 모든 짐을 짊어진 Serivce 계층&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쇼핑몰에서 주문 취소 로직을 구현한다고 가정해 보자. 전통적인 트랜젝션 스크랩트(Transaction Script) 패턴에서는 서비스 계층이 모든 비즈니스 규칙을 검사하고 데이터의 상태를 직접 변경한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Bad Code : 빈약한 도메인 모델 (Anemic Domain Model)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;//&amp;nbsp;Entity는&amp;nbsp;그저&amp;nbsp;데이터를&amp;nbsp;담는&amp;nbsp;바구니(C&amp;nbsp;데이터&amp;nbsp;구조)에&amp;nbsp;불과하다. &lt;br /&gt;@Entity &lt;br /&gt;public&amp;nbsp;class&amp;nbsp;Order&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;Long&amp;nbsp;id; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;OrderStatus&amp;nbsp;status; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;int&amp;nbsp;totalAmount; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;무분별하게&amp;nbsp;열려있는&amp;nbsp;Getter&amp;nbsp;/&amp;nbsp;Setter &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;OrderStatus&amp;nbsp;getStatus()&amp;nbsp;{&amp;nbsp;return&amp;nbsp;status;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;setStatus(OrderStatus&amp;nbsp;status)&amp;nbsp;{&amp;nbsp;this.status&amp;nbsp;=&amp;nbsp;status;&amp;nbsp;} &lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;@Service &lt;br /&gt;public&amp;nbsp;class&amp;nbsp;OrderService&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Transactional &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;cancelOrder(Long&amp;nbsp;orderId)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Order&amp;nbsp;order&amp;nbsp;=&amp;nbsp;orderRepository.findById(orderId).orElseThrow(); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;1.&amp;nbsp;서비스&amp;nbsp;계층에서&amp;nbsp;비즈니스&amp;nbsp;규칙(상태&amp;nbsp;검증)을&amp;nbsp;직접&amp;nbsp;확인한다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(order.getStatus()&amp;nbsp;==&amp;nbsp;OrderStatus.SHIPPED)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw&amp;nbsp;new&amp;nbsp;IllegalStateException(&quot;이미&amp;nbsp;배송된&amp;nbsp;주문은&amp;nbsp;취소할&amp;nbsp;수&amp;nbsp;없습니다.&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;2.&amp;nbsp;서비스&amp;nbsp;계층에서&amp;nbsp;엔티티의&amp;nbsp;상태를&amp;nbsp;직접&amp;nbsp;변경한다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;order.setStatus(OrderStatus.CANCELED); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;orderRepository.save(order); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;문제점&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Order 객체는 자신의 상태를 스스로 제어하지 못하는 수동적인 존재다. 만약 다른 서비스에서도 주문 취소 기능이 필요하다면, 저 검증 로직 코드가 여기저기 중복해서 복사될 확률이 높다. 결국 &lt;b&gt;객체 지향적이지 않은 절차지향적 코드&lt;/b&gt;가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 해결책 : 풍부한 도메인 모델 (Rich Domain Model)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DDD의 핵심 전술 중 하나는 &lt;b&gt;데이터를 가진 객체가 비즈니스 로직도 함께 가져야 한다&lt;/b&gt;는 것이다. Serivce는 그저 도메인 객체를 불러오고, 도메인 객체에게 명령을 내리는 오케스트레이터(Orchestrator) 역할만 수행해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Good Code: 도메인 객체 스스로 행동하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;//&amp;nbsp;Entity가&amp;nbsp;비즈니스&amp;nbsp;로직의&amp;nbsp;중심이&amp;nbsp;된다.&amp;nbsp;(Rich&amp;nbsp;Domain&amp;nbsp;Model) &lt;br /&gt;@Entity &lt;br /&gt;public&amp;nbsp;class&amp;nbsp;Order&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;Long&amp;nbsp;id; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;OrderStatus&amp;nbsp;status; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;int&amp;nbsp;totalAmount; &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;1.&amp;nbsp;Setter는&amp;nbsp;닫아두고,&amp;nbsp;명확한&amp;nbsp;의도를&amp;nbsp;가진&amp;nbsp;메서드를&amp;nbsp;열어둔다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;cancel()&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;verifyNotShipped();&amp;nbsp;//&amp;nbsp;자기&amp;nbsp;자신의&amp;nbsp;상태는&amp;nbsp;스스로&amp;nbsp;검증한다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.status&amp;nbsp;=&amp;nbsp;OrderStatus.CANCELED; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;2.&amp;nbsp;도메인&amp;nbsp;규칙(비즈니스&amp;nbsp;로직)이&amp;nbsp;엔티티&amp;nbsp;내부에&amp;nbsp;응집된다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;void&amp;nbsp;verifyNotShipped()&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(this.status&amp;nbsp;==&amp;nbsp;OrderStatus.SHIPPED)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw&amp;nbsp;new&amp;nbsp;IllegalStateException(&quot;이미&amp;nbsp;배송된&amp;nbsp;주문은&amp;nbsp;취소할&amp;nbsp;수&amp;nbsp;없습니다.&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;@Service &lt;br /&gt;public&amp;nbsp;class&amp;nbsp;OrderService&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Transactional &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;cancelOrder(Long&amp;nbsp;orderId)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Order&amp;nbsp;order&amp;nbsp;=&amp;nbsp;orderRepository.findById(orderId).orElseThrow(); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;서비스는&amp;nbsp;그저&amp;nbsp;도메인&amp;nbsp;객체에게&amp;nbsp;행동을&amp;nbsp;위임(Delegation)할&amp;nbsp;뿐이다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;order.cancel();&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 주문 취소에 대한 비즈니스 규칙은 오직 Order 클래스 내부에서만 관리한다. 응집도가 극대화되며, 코드를 읽었을 때 도메인의 규칙이 한눈에 파악된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. DDD의 주요 구성 요소 (전술적 설계)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비즈니스 로직을 도메인으로 옮기는 과정에서 몇 가지 중요한 개념들을 활용하여 도메인을 모델링 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;엔티티(Entity)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;고유한 식별자(ID)&lt;/b&gt;를 갖는 객체&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 속성값이 바뀌어도 식별자가 같다면 동일한 객체로 취급&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 위 예제의 Order, Member 등이 해당&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;값 객체(Value Object, VO)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 식별자가 없고, &lt;b&gt;속성값 자체가 식별자&lt;/b&gt;인 객체&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 데이터의 불변성을 보장해야 하며, 값이 같으면 완전히 같은 객체로 취급 &lt;b&gt;(equals &amp;amp; hashCode 재정의 필수)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 예: Address(city, street, zipcode), Money(amount, currency)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;에그리거트(Aggregate)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 관련된 엔티티와 값 객체들의 묶음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;데이터의 정합성을 유지하는 한 단위&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 루트 엔티티(Aggregate Root)를 통해서만 에그리거트 내부의 객체에 접근하고 수정 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 예: Order 애그리거트는 내부적으로 OrderLine(주문 항목), ShippingInfo(배송 정보)를 가질 수 있으며, 외부에서는 오직 Order를 통해서만 내부 상태를 변경해야 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 주의사항&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무작정 모든 프로젝트에 DDD를 도입하려고 하면 배보다 배꼽이 더 커지는 오버엔지니어링(Over-engineering)을 겪게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;단순 CRUD에는 사치다&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게시판의 공지사항 읽기/쓰기 처럼 비즈니스 로직이 거의 없는 시스템에는 기존의 트랜젝션 스크립트 구조(데이터 중심 설계)가 훨씬 빠르고 효율적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;전술보다 전략이 우선이다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패키지 구조를 나누고 엔티티를 설계하는 전술적 설계보다 중요한 것은, 실무자와 개발자가 동일한 언어를 사용하는 &lt;b&gt;보편적 언어&lt;/b&gt;의 확립과 도메인의 경계를 명확히 나누는 바운디드 &lt;b&gt;컨텍스트(Bounded Context)&lt;/b&gt;를 설정하는 전략적 설계다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도메인 주도 설계(DDD)의 본질은 복잡한 소프트웨어 설계 패턴을 암기하는 것이 아니다. &lt;b&gt;우리가 해결하고자 하는 현실 세계의 비즈니스 도메인을 코드에 얼마나 객체지향적으로 잘 녹여내는가&lt;/b&gt;에 대한 끊임 없는 고민의 결과물이다.&lt;/p&gt;</description>
      <category>Software Engineering</category>
      <category>architecture</category>
      <category>DDD</category>
      <category>DomainDrivenDesign</category>
      <category>SpringBoot</category>
      <category>SpringFramework</category>
      <category>객체지향</category>
      <category>도메인주도설계</category>
      <category>아키텍처</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/255</guid>
      <comments>https://hsunnystory.tistory.com/255#entry255comment</comments>
      <pubDate>Thu, 26 Mar 2026 10:48:12 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] API 서버는 단일 스레드일까? (HashMap vs ConcurrentHashMap 실전 적용)</title>
      <link>https://hsunnystory.tistory.com/254</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;우리 API 서버는 요청을 동시에 여러 개 처리하는데, 이건 단일 스레드인가요? 멀티 스레드인가요?&quot;&lt;/b&gt;&lt;br /&gt;실무에서 주니어 개발자들의 코드르 리뷰하다 보면 가장 많이 발견되는 오해 중 하나다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론부터 명확하게 짚고 넘어가자면 &lt;b&gt;우리가 흔히 쓰는 Spring Boot(Tomcat) API 서버는 완벽한 멀티 스레드 환경이다.&lt;/b&gt; 따라서 API 서버에 동시에 여러 요청이 들어온다는 것 자체가 이미 수많은 스레드가 동시에 돌아가고 있다는 뜻이다. 이번 포스팅에서는 API 서버 내부에서 스레드가 어떻게 동작하는지, 그리고 어떤 상황에서 HashMap을 써도 되고 언제 ConcurrentHashMap을 써야하는지 알아본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://hsunnystory.tistory.com/253&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://hsunnystory.tistory.com/253&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1774229394942&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Java] ConcurrentHashMap 이란?&quot; data-og-description=&quot;백엔드 시스템을 개발하다 보면 여러 Thread에서 동시에 접근해야 하는 인메모리 캐시나 세션 저장소를 구현해야 할 때가 있다. 이 때 가장 만만하게 쓰는 자료구조가 바로 Map이다. 필자도 그랬듯&quot; data-og-host=&quot;hsunnystory.tistory.com&quot; data-og-source-url=&quot;https://hsunnystory.tistory.com/253&quot; data-og-url=&quot;https://hsunnystory.tistory.com/253&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/DpFKB/dJMb9lMaTqD/gzYGD8l6lZNjo5URJEFop0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/D8eoC/dJMb9cBHtRU/YYUxkdV0mMsX9wvuJakUPk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://hsunnystory.tistory.com/253&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hsunnystory.tistory.com/253&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/DpFKB/dJMb9lMaTqD/gzYGD8l6lZNjo5URJEFop0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/D8eoC/dJMb9cBHtRU/YYUxkdV0mMsX9wvuJakUPk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Java] ConcurrentHashMap 이란?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;백엔드 시스템을 개발하다 보면 여러 Thread에서 동시에 접근해야 하는 인메모리 캐시나 세션 저장소를 구현해야 할 때가 있다. 이 때 가장 만만하게 쓰는 자료구조가 바로 Map이다. 필자도 그랬듯&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hsunnystory.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 오해 : API 서버는 멀티 스레드 환경이다 (Tomcat Thread Pool)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Node.js 같은 특별한 Non-blocking 싱글 스레드 환경이 아니라면, Java 계열의 Spring Boot(Tomcat)는 Thread Pool) 방식을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 서버가 켜지면 Tomcat은 미리 스레드를 여러 개 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 사용자 A가 API를 호출하면, Thread Pool에서 &lt;b&gt;Thread-1&lt;/b&gt;을 꺼내서 할당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 동시에 사용자 B가 API를 호출하면, &lt;b&gt;Thread-2&lt;/b&gt;을 꺼내서 할당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 &lt;b&gt;동시에 들어오는 API 요청 개수 = 동시에 실행되는 스레드의 개수&lt;/b&gt;다. 따라서 당연히 멀티 스레드 환경이므로 동시성 문제를 고려해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 그렇다면 API 서버에서는 무조건 ConcurrentHashMap만 써야 할까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론부터 말하면 절반만 맞은것이다. 핵심은 &lt;b&gt;해당 Map 객체가 여러 Thread에 의해 공유되는 자원인지&lt;/b&gt;를 파악하는 것이다. 메모리 영역(Stack vs Heap)의 관점에서 두 가지 상황으로 나누어 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;상황 A: 지역 변수로 사용하는 경우 (HashMap 사용)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드 내부에서 생성된 변수는 각 Thread만의 독립적인 공간인 &lt;b&gt;Stack 메모리&lt;/b&gt;에 저장된다. 즉, Thread-1과 Thread-2이 같은 메서드를 호출하더라도, 각자 자기만의 HashMap을 새로 만들어서 쓰기 때문에 절대 충돌이 일어나지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Service &lt;br /&gt;public&amp;nbsp;class&amp;nbsp;OrderService&amp;nbsp;{ &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;processOrder()&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;[Good&amp;nbsp;Code]&amp;nbsp;지역&amp;nbsp;변수:&amp;nbsp;스레드마다&amp;nbsp;각자의&amp;nbsp;Map을&amp;nbsp;생성하므로&amp;nbsp;매우&amp;nbsp;안전하다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;동기화&amp;nbsp;비용이&amp;nbsp;없는&amp;nbsp;가장&amp;nbsp;빠른&amp;nbsp;HashMap을&amp;nbsp;쓰는&amp;nbsp;것이&amp;nbsp;정답이다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Map&amp;lt;String,&amp;nbsp;Object&amp;gt;&amp;nbsp;localMap&amp;nbsp;=&amp;nbsp;new&amp;nbsp;HashMap&amp;lt;&amp;gt;(); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;localMap.put(&quot;status&quot;,&amp;nbsp;&quot;READY&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;...&amp;nbsp;비즈니스&amp;nbsp;로직&amp;nbsp;... &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;상황 B: 전역변수로 사요하는 경우(ConcurrentHashMap 사용)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring의 @Controller, @Service, @Repository 같은 Bean들은 기본적으로 &lt;b&gt;Singleton&lt;/b&gt;으로 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 애플리케이션 전체에 딱 1개만 존재하며 Heap 메모리에 올라간다. 수백 개의 Thread가 이 1개의 서비스 객체에 동시에 접근한게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Service &lt;br /&gt;public&amp;nbsp;class&amp;nbsp;CacheService&amp;nbsp;{ &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;[Good&amp;nbsp;Code]&amp;nbsp;멀티&amp;nbsp;스레드가&amp;nbsp;공유하는&amp;nbsp;전역&amp;nbsp;상태이므로&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;부분&amp;nbsp;잠금을&amp;nbsp;지원하는&amp;nbsp;ConcurrentHashMap을&amp;nbsp;사용해야&amp;nbsp;한다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;Map&amp;lt;String,&amp;nbsp;Integer&amp;gt;&amp;nbsp;globalCache&amp;nbsp;=&amp;nbsp;new&amp;nbsp;ConcurrentHashMap&amp;lt;&amp;gt;(); &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;addCache(String&amp;nbsp;key,&amp;nbsp;int&amp;nbsp;value)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;globalCache.put(key,&amp;nbsp;value);&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 아키텍처 원칙: Stateless 설계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 가장 좋은 방법은 &lt;b&gt;스프링 Bean 안에 아에 전역 변수를 두지 않는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 데이터를 캐싱해야 한다면 애플리케잉션 메모리(Map)에 들고 있는 것보다, 외부의 Redis나 Memcached 같은 전문 캐시 서버에 위임하는 것이 훨씬 안전하고 서버 확장에 유리하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 부득이하게 애플리케이션 내부 메모리에 데이터를 들고 있어야 하는 경우에만 제한적으로 ConcurrentHashMap을 사용하는 것이 바람직하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;Spring API 서버는 기본적으로 Tomcat Thread Pool을 사용하는 Multi-Thread 환경이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 메서드 내부의 &lt;b&gt;지역변수&lt;/b&gt;로 Map을 쓸 때는 동시성 문제가 없으므로 제일 빠른 &lt;b&gt;HashMap&lt;/b&gt;을 쓴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- Spring Bean(@Service)등의 &lt;b&gt;멤버 변수&lt;/b&gt;로 Map을 선언하여 여러 요청이 공유하게 만들 때는 반드시 &lt;b&gt;ConcurrentMap&lt;/b&gt;을 사용한다.&lt;/p&gt;</description>
      <category>Web Programming/Spring</category>
      <category>ConcurrentHashMap</category>
      <category>HashMap</category>
      <category>multithread</category>
      <category>Spring</category>
      <category>SpringBoot</category>
      <category>ThreadPool</category>
      <category>tomcat</category>
      <category>동시성</category>
      <category>멀티스레드</category>
      <category>스레드풀</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/254</guid>
      <comments>https://hsunnystory.tistory.com/254#entry254comment</comments>
      <pubDate>Mon, 23 Mar 2026 10:41:57 +0900</pubDate>
    </item>
    <item>
      <title>[Java] ConcurrentHashMap 이란?</title>
      <link>https://hsunnystory.tistory.com/253</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 시스템을 개발하다 보면 여러 Thread에서 동시에 접근해야 하는 &lt;b&gt;인메모리 캐시&lt;/b&gt;나 &lt;b&gt;세션 저장소&lt;/b&gt;를 구현해야 할 때가 있다. 이 때 가장 만만하게 쓰는 자료구조가 바로 Map이다. 필자도 그랬듯이 아무 생각 없이 HashMap을 사용했다가, 데이터 유실이나 무한 루프(Java 7 이하)로 인해 서버가 뻗어버리는 대참사를 겪게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 멀티스레드와 HashMap의 충돌&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 스레드가 동시에 접속하는 트래픽 환경에서, 전역 변수로 선언된 HashMap에 데이터를 읽고 쓰는 상황을 가정해보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Bad Code 1: 스레드 안정성(Thread-Safe)이 없는 HashMap&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public&amp;nbsp;class&amp;nbsp;CacheService&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;여러&amp;nbsp;스레드가&amp;nbsp;동시에&amp;nbsp;접근하는&amp;nbsp;공유&amp;nbsp;자원 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;Map&amp;lt;String,&amp;nbsp;Integer&amp;gt;&amp;nbsp;cache&amp;nbsp;=&amp;nbsp;new&amp;nbsp;HashMap&amp;lt;&amp;gt;(); &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;addCache(String&amp;nbsp;key,&amp;nbsp;int&amp;nbsp;value)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;멀티스레드&amp;nbsp;환경에서&amp;nbsp;동시에&amp;nbsp;put()이&amp;nbsp;호출되면&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;데이터가&amp;nbsp;덮어씌워지거나,&amp;nbsp;내부&amp;nbsp;노드&amp;nbsp;링크가&amp;nbsp;꼬여버린다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cache.put(key,&amp;nbsp;value);&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HashMap은 동기화(Synchronization) 처리가 전혀 되어 있지 않다. 두 스레드가 동시에 같은 해시 노드에 데이터를 삽입하려고 하면 경쟁 상태가 발생하여 데이터가 유실된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Bad Code 2: 무식한 동기화, HashTable과 SynchronizedMap&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시성 문제를 해결하기 위해 자바 레거시의 잔재인 HashTable이나 Collections.synchronizedMap()을 사용하는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public&amp;nbsp;class&amp;nbsp;CacheService&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;스레드&amp;nbsp;안전성을&amp;nbsp;보장하지만&amp;nbsp;성능이&amp;nbsp;최악인&amp;nbsp;방식 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;Map&amp;lt;String,&amp;nbsp;Integer&amp;gt;&amp;nbsp;cache&amp;nbsp;=&amp;nbsp;Collections.synchronizedMap(new&amp;nbsp;HashMap&amp;lt;&amp;gt;()); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;혹은&amp;nbsp;private&amp;nbsp;Map&amp;lt;String,&amp;nbsp;Integer&amp;gt;&amp;nbsp;cache&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Hashtable&amp;lt;&amp;gt;(); &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;addCache(String&amp;nbsp;key,&amp;nbsp;int&amp;nbsp;value)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cache.put(key,&amp;nbsp;value); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이들은 내부적으로 모든 get(), put() 메서드에 전체 맵(Map) 단위의 &lt;b&gt;잠금&lt;/b&gt;을 걸어버린다. 한 스레드가 데이터를 읽기만 하고 있어도, 다른 모든 스레드는 대기 상태에 빠지게 되어 심각한 &lt;b&gt;병목현상&lt;/b&gt;을 유발한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 해결책 : ConcurrentHashMap&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터의 안전성도 지키면서, 성능까지 끌어올리기 위해 ConcurrentHashMap를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public&amp;nbsp;class&amp;nbsp;CacheService&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;동시성&amp;nbsp;보장&amp;nbsp;+&amp;nbsp;뛰어난&amp;nbsp;성능 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;Map&amp;lt;String,&amp;nbsp;Integer&amp;gt;&amp;nbsp;cache&amp;nbsp;=&amp;nbsp;new&amp;nbsp;ConcurrentHashMap&amp;lt;&amp;gt;(); &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;addCache(String&amp;nbsp;key,&amp;nbsp;int&amp;nbsp;value)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cache.put(key,&amp;nbsp;value);&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;int&amp;nbsp;getCache(String&amp;nbsp;key)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;cache.get(key);&amp;nbsp;//&amp;nbsp;읽기&amp;nbsp;작업에는&amp;nbsp;Lock이&amp;nbsp;걸리지&amp;nbsp;않아&amp;nbsp;매우&amp;nbsp;빠르다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. ConcurrentHashMap의 특징&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 맵에 락을 거는 HashTable과 달리, ConcurrentHashMap은 영리한 동기화 전략을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Node 단위 부분 잠금 (Lock Striping / Node Locking)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ConcurrentHashMap은 맵 전체가 아니라, &lt;b&gt;데이터가 삽입되는 해당 노드에만 잠금&lt;/b&gt;을 건다. 만약 스레드 A가 1번 노드에 데이터를 쓰고 있더라도, 스레드 B가 5번 노드에 데이터를 쓰는 작업은 전혀 방해받지 않고 동시에 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CAS(Compare And Swap) 알고리즘 적용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 노드를 삽입할 때, 해당 노드가 비어있다면 &lt;b&gt;synchronized&lt;/b&gt; 락을 거는 대신 가벼운 &lt;b&gt;CAS 알고리즘&lt;/b&gt;을 사용하여 원자적으로 값을 삽입한다. CAS는&amp;nbsp;&lt;b&gt;현재&amp;nbsp;메모리의&amp;nbsp;값에&amp;nbsp;내가&amp;nbsp;기대하는&amp;nbsp;값과&amp;nbsp;같을&amp;nbsp;때만&amp;nbsp;새로운&amp;nbsp;값으로&amp;nbsp;교체&lt;/b&gt;하는&amp;nbsp;하드웨어&amp;nbsp;지원&amp;nbsp;원자적연산으로,&amp;nbsp;락을&amp;nbsp;획득하고&amp;nbsp;해제하는&amp;nbsp;오버헤드가&amp;nbsp;없어&amp;nbsp;매우&amp;nbsp;빠르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;읽기(Read) 작업은 Lock-Free&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 중요한 장점으로 get()으로 데이터를 읽어올 때는 &lt;b&gt;Lock을 전혀 사용하지 않는다.&lt;/b&gt; 내부 데이터 구조에 volatile 키워드를 활용하여, 항상 최신 상태의 데이터를 읽어올 수 있도록 가시성을 보장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티스레드 환경에서 Map 자료구조가 필요하다면, 고민하지 말고 ConcurrentHashMap를 쓰자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;단일 스레드 환경&lt;/b&gt; : HashMap (동기화 비용 없이 제일 빠름)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;멀티 스레드 환경&lt;/b&gt; : ConcurrentHashMap (부분 잠금 및 CAS 알고리즘으로 안전성과 성능 동시 확보)&lt;/p&gt;</description>
      <category>Programming/Java</category>
      <category>ConcurrentHashMap</category>
      <category>memory cache</category>
      <category>multithread</category>
      <category>session Storage</category>
      <category>threadsafe</category>
      <category>동시성</category>
      <category>멀티스레드</category>
      <category>세션 저장소</category>
      <category>인메모리 캐시</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/253</guid>
      <comments>https://hsunnystory.tistory.com/253#entry253comment</comments>
      <pubDate>Tue, 17 Mar 2026 15:28:37 +0900</pubDate>
    </item>
    <item>
      <title>[Design Pattern] 빌더 패턴(Builder Pattern) 란?</title>
      <link>https://hsunnystory.tistory.com/252</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;개발을 하다 보면, 필드가 수십 개에 달하는 복잡한 객체를 생성해야 할 때가 많다. 특히 외부 시스템과 연동하거나, 인증 및 보안 관련 데이터를 다룰 때 더욱 그렇다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 문제상황&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IoT 기기 인증 시스템을 개발한다고 가정해보자. IotDevice라는 객체는 필수적인 필드(기기 ID, MAC)와 선택적인 필드(펌웨어 버전, 암호화 타입, 네트워크 상태 등)를 다양하게 가진다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Bad Code 1: 점층적 생성자 패턴 (Telescoping Constructor)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 원시적인 방법은 매개변수의 개수를 늘려가며 생성자를 오버로딩(Overloading)하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public&amp;nbsp;class&amp;nbsp;IotDevice&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;String&amp;nbsp;deviceId;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;필수 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;String&amp;nbsp;macAddress;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;필수 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;String&amp;nbsp;firmwareVer;&amp;nbsp;&amp;nbsp;//&amp;nbsp;선택 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;String&amp;nbsp;cryptoType;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;선택 &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;필수&amp;nbsp;필드만&amp;nbsp;받는&amp;nbsp;생성자 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;IotDevice(String&amp;nbsp;deviceId,&amp;nbsp;String&amp;nbsp;macAddress)&amp;nbsp;{&amp;nbsp;...&amp;nbsp;} &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;필수&amp;nbsp;+&amp;nbsp;선택&amp;nbsp;필드&amp;nbsp;1개 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;IotDevice(String&amp;nbsp;deviceId,&amp;nbsp;String&amp;nbsp;macAddress,&amp;nbsp;String&amp;nbsp;firmwareVer)&amp;nbsp;{&amp;nbsp;...&amp;nbsp;} &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;모든&amp;nbsp;필드를&amp;nbsp;받는&amp;nbsp;생성자 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;IotDevice(String&amp;nbsp;deviceId,&amp;nbsp;String&amp;nbsp;macAddress,&amp;nbsp;String&amp;nbsp;firmwareVer,&amp;nbsp;String&amp;nbsp;cryptoType)&amp;nbsp;{&amp;nbsp;...&amp;nbsp;} &lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;//&amp;nbsp;클라이언트&amp;nbsp;사용&amp;nbsp;시:&amp;nbsp;인자의&amp;nbsp;의미와&amp;nbsp;순서를&amp;nbsp;파악하기&amp;nbsp;매우&amp;nbsp;어렵다. &lt;br /&gt;IotDevice&amp;nbsp;device&amp;nbsp;=&amp;nbsp;new&amp;nbsp;IotDevice(&quot;DEV-001&quot;,&amp;nbsp;&quot;00:1A:2B...&quot;,&amp;nbsp;null,&amp;nbsp;&quot;RSA_2048&quot;);&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매개변수가 많아질수록 클라이언트 코드를 작성하거나 읽기 어렵다. 특히 같은 타입(String)이 연속될 경우, 실수로 순서를 바꿔치기 해도 컴퍼일러가 잡아내지 못해 런타임 에러로 이어진다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Bad Code 2: 자바빈즈 패턴(JavaBeans Pattern)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하기 위해 기본 생성바로 빈 객체를 만든 후, Setter 메서드를 호출하여 값을 채워 넣는 방식을 많이 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IotDevice&amp;nbsp;device&amp;nbsp;=&amp;nbsp;new&amp;nbsp;IotDevice(); &lt;br /&gt;device.setDeviceId(&quot;DEV-001&quot;); &lt;br /&gt;device.setMacAddress(&quot;00:1A:2B:3C:4D:5E&quot;); &lt;br /&gt;device.setFirmwareVer(&quot;v1.2.4&quot;); &lt;br /&gt;//&amp;nbsp;...&amp;nbsp;계속되는&amp;nbsp;Setter&amp;nbsp;호출&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 객체의 일관성 붕괴 :1회의 호출로 객체 생성이 끝나지 않는다. 필수 값이 모두 세팅되지 않은 상태로 객체가 중간에 사용될 위험이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 불변성 상실 : Setter가 열려있으므로, 누군가 런타임에 객체의 상태를 변경할 수 있어 버그 추적이 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 해결책 : 빌더 패턴&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌더 패턴은 점층적 생성자의 &lt;b&gt;안전성&lt;/b&gt;과 자바빈즈 패턴의 &lt;b&gt;가독성&lt;/b&gt;을 모두 취한 대안이다. 객체를 직접 생성하는 대신, 필수 매개변수만으로 빌더(Builder) 객체를 얻은 후, 빌더가 제공하는 일종의 Setter 메서드들로 선택 매개변수를 세팅한다. 마지막으로 build() 메서드를 호출하여 완전한 그리고 불변인 객체를 얻는다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Good Code : 정적 내부 클래스 (Static Inner Class)를 활용한 빌더 구현&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public&amp;nbsp;class&amp;nbsp;IotDevice&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;모든&amp;nbsp;필드는&amp;nbsp;final로&amp;nbsp;선언하여&amp;nbsp;불변성(Immutability)을&amp;nbsp;보장한다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;String&amp;nbsp;deviceId; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;String&amp;nbsp;macAddress; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;String&amp;nbsp;firmwareVer; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;String&amp;nbsp;cryptoType; &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;private&amp;nbsp;생성자:&amp;nbsp;오직&amp;nbsp;Builder를&amp;nbsp;통해서만&amp;nbsp;객체를&amp;nbsp;생성할&amp;nbsp;수&amp;nbsp;있게&amp;nbsp;막는다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;IotDevice(Builder&amp;nbsp;builder)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.deviceId&amp;nbsp;=&amp;nbsp;builder.deviceId; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.macAddress&amp;nbsp;=&amp;nbsp;builder.macAddress; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.firmwareVer&amp;nbsp;=&amp;nbsp;builder.firmwareVer; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.cryptoType&amp;nbsp;=&amp;nbsp;builder.cryptoType; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;---&amp;nbsp;정적&amp;nbsp;내부&amp;nbsp;클래스&amp;nbsp;Builder&amp;nbsp;--- &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;static&amp;nbsp;class&amp;nbsp;Builder&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;필수&amp;nbsp;필드 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;String&amp;nbsp;deviceId; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;String&amp;nbsp;macAddress; &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;선택&amp;nbsp;필드&amp;nbsp;(기본값으로&amp;nbsp;초기화&amp;nbsp;가능) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;String&amp;nbsp;firmwareVer&amp;nbsp;=&amp;nbsp;&quot;v1.0.0&quot;; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;String&amp;nbsp;cryptoType&amp;nbsp;=&amp;nbsp;&quot;NONE&quot;; &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;필수&amp;nbsp;필드는&amp;nbsp;Builder의&amp;nbsp;생성자로&amp;nbsp;받는다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;Builder(String&amp;nbsp;deviceId,&amp;nbsp;String&amp;nbsp;macAddress)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.deviceId&amp;nbsp;=&amp;nbsp;deviceId; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.macAddress&amp;nbsp;=&amp;nbsp;macAddress; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;선택&amp;nbsp;필드는&amp;nbsp;메서드&amp;nbsp;체이닝(Method&amp;nbsp;Chaining)을&amp;nbsp;위해&amp;nbsp;자기&amp;nbsp;자신(Builder)을&amp;nbsp;반환한다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;Builder&amp;nbsp;firmwareVer(String&amp;nbsp;firmwareVer)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.firmwareVer&amp;nbsp;=&amp;nbsp;firmwareVer; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;this; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;Builder&amp;nbsp;cryptoType(String&amp;nbsp;cryptoType)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.cryptoType&amp;nbsp;=&amp;nbsp;cryptoType; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;this; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;최종&amp;nbsp;객체&amp;nbsp;생성&amp;nbsp;(여기서&amp;nbsp;유효성&amp;nbsp;검증&amp;nbsp;로직을&amp;nbsp;추가할&amp;nbsp;수&amp;nbsp;있다) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;IotDevice&amp;nbsp;build()&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;new&amp;nbsp;IotDevice(this); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;클라이언트 코드 (빌더 사용)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public&amp;nbsp;class&amp;nbsp;Main&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;static&amp;nbsp;void&amp;nbsp;main(String[]&amp;nbsp;args)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;메서드&amp;nbsp;체이닝을&amp;nbsp;통해&amp;nbsp;가독성이&amp;nbsp;비약적으로&amp;nbsp;상승한다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;IotDevice&amp;nbsp;authDevice&amp;nbsp;=&amp;nbsp;new&amp;nbsp;IotDevice.Builder(&quot;DEV-999&quot;,&amp;nbsp;&quot;AA:BB:CC:DD:EE:FF&quot;) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.firmwareVer(&quot;v2.1.0&quot;) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.cryptoType(&quot;ECC_P256&quot;) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build(); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 장점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;직관적인 가독성&lt;/b&gt; : 어떤 필드에 어떤 값이 들어가는지 명확하게 보인다. 인자의 순서에 구애받지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;객체의 불변성(Immutable) 보장&lt;/b&gt; : Setter가 없으므로 생성된 객체는 스레드 안전(Thread-safe)하며 상태가 변경되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;유효성 검증의 용이성&lt;/b&gt; : build() 메서드 내부에서 필수 값 누락이나 제약 조건 위반 등을 한 번에 검증할 수 있어 객체의 일관성을 유지할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. Lombok과 @Builder&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매번 위와 같이 긴 보일러플레이트(Boilerplate) 코드를 수작업으로 작성하기 번거롭다. Java에서는 &lt;b&gt;Lombok&lt;/b&gt; 라이브러리의 @Builder 어노테이션을 활용하면 컴파일 타임에 자동으로 완벽한 빌더 클래스를 생성해 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;import&amp;nbsp;lombok.Builder; &lt;br /&gt;&lt;br /&gt;@Builder &lt;br /&gt;public&amp;nbsp;class&amp;nbsp;IotDevice&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;String&amp;nbsp;deviceId; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;String&amp;nbsp;macAddress; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;String&amp;nbsp;firmwareVer; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;String&amp;nbsp;cryptoType; &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, Lombok을 사용할 때도 내부적으로 어떤 구조로 객체가 캡슐화되어 생성되는지 그 원리를 명확히 이해하고 쓰는 것과 모르고 쓰는 것은 아키텍처 설계 기능을 가르는 큰 차이가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자의 매개변수가 4개 이상 넘어가거나, 앞으로 필드가 계속 추가될 가능성이 있다면 망설이지 말고 &lt;b&gt;빌더 패턴&lt;/b&gt;을 도입하자. 코드를 작성할 때는 조금 더 수고스럽더라도, 미래의 나와 동료들이 코드를 읽고 유지보수할 때의 고통을 획기적으로 줄여줄 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Programming/Design Pattern</category>
      <category>BuilderPattern</category>
      <category>DesignPattern</category>
      <category>Java</category>
      <category>OOP</category>
      <category>Spring</category>
      <category>디자인패턴</category>
      <category>빌더패턴</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/252</guid>
      <comments>https://hsunnystory.tistory.com/252#entry252comment</comments>
      <pubDate>Fri, 6 Mar 2026 11:36:20 +0900</pubDate>
    </item>
    <item>
      <title>[Design Pattern] 데코레이터 패턴(Decorator Pattern)이란?</title>
      <link>https://hsunnystory.tistory.com/251</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;개발을 진행하다 보면 이미 완성된 객체에 새로운 기능을 동적으로 추가해야 할 때가 많다. 가장 먼저 떠오르는 방법은 &lt;b&gt;상속(Inheritance)&lt;/b&gt;이지만, 요구사항이 복잡해질수록 상속만으로는 감당하기 어려운 &lt;b&gt;클래스 폭발(Class Explosion)&lt;/b&gt; 문제에 직면하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 상속의 한계를 극복하고, 객체를 유연하게 확장할 수 있는 &lt;b&gt;데코레이터 패턴(Decorator Pattern)&lt;/b&gt;에 대해 알아본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 데코레이터 패턴이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데코레이터 패턴은 객체에 추가적인 기능을 &lt;b&gt;동적으로 덧붙이는(Wrapping)&lt;/b&gt; 패턴이다. 서브클래스를 만드는 대신, 객체를 감싸는 장식자(Decorator) 클래스를 두어 런타임에 유연하게 기능을 확장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- Component : 기본 기능을 정의하는 최상의 인터페이스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- ConcreteComponent : 기본 기능을 구현하는 실제 핵심 객체&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- Decorator : Component를 구현하면서(IS-A), 내부에 Component 객체를 품고 있는(HAS-A) 추상 클래스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- ConcreteDecorator : 실제 부가 기능을 덧붙이는 장식자 클래스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 상속의 한계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자에게 알림을 보내는 시스템을 개발한다고 가정해보자. 초기에는 이메일 알림만 필요해서 Notifier 라는 기본 클래스를 만들었다. 하지만 요구사항은 늘 변한다. SMS 알림도 추가해야 할 수도 있고, 이메일 알림도 추가해야 할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Bad Code: 상속을 남용한 경우&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;//&amp;nbsp;기본&amp;nbsp;알림 &lt;br /&gt;class&amp;nbsp;EmailNotifier&amp;nbsp;{&amp;nbsp;...&amp;nbsp;} &lt;br /&gt;&lt;br /&gt;//&amp;nbsp;요구사항이&amp;nbsp;늘어날&amp;nbsp;때마다&amp;nbsp;무한히&amp;nbsp;증식하는&amp;nbsp;서브클래스들 &lt;br /&gt;class&amp;nbsp;SmsNotifier&amp;nbsp;extends&amp;nbsp;EmailNotifier&amp;nbsp;{&amp;nbsp;...&amp;nbsp;} &lt;br /&gt;class&amp;nbsp;SlackNotifier&amp;nbsp;extends&amp;nbsp;EmailNotifier&amp;nbsp;{&amp;nbsp;...&amp;nbsp;} &lt;br /&gt;class&amp;nbsp;SmsAndEmailNotifier&amp;nbsp;extends&amp;nbsp;EmailNotifier&amp;nbsp;{&amp;nbsp;...&amp;nbsp;} &lt;br /&gt;class&amp;nbsp;SlackAndSmsAndEmailNotifier&amp;nbsp;extends&amp;nbsp;EmailNotifier&amp;nbsp;{&amp;nbsp;...&amp;nbsp;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 방식의 치명적인 단점은 기능의 조합(Combination)이 발생할 때마다 새로운 클래스를 하드코딩해야 한다는 점이다. 옵션이 몇 개만 늘어나도 수십 개의 클래스가 생성되어 유지보수가 불가능해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 데코레이터 패턴 적용&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기본기능 정의(Component &amp;amp; ConcreteComponent)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;//&amp;nbsp;1.&amp;nbsp;Component:&amp;nbsp;공통&amp;nbsp;인터페이스 &lt;br /&gt;public&amp;nbsp;interface&amp;nbsp;Notifier&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;void&amp;nbsp;send(String&amp;nbsp;message); &lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;//&amp;nbsp;2.&amp;nbsp;ConcreteComponent:&amp;nbsp;기본&amp;nbsp;알림(이메일) &lt;br /&gt;public&amp;nbsp;class&amp;nbsp;BasicNotifier&amp;nbsp;implements&amp;nbsp;Notifier&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Override &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;send(String&amp;nbsp;message)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&quot;이메일&amp;nbsp;전송:&amp;nbsp;&quot;&amp;nbsp;+&amp;nbsp;message); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;장식자 구현 (Decorator)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데코레이터 패턴의 핵심이다. 자신이 장식할 대상을 주입받아 보관하고, 기본 요청은 그 대상에게 위임한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;//&amp;nbsp;3.&amp;nbsp;Decorator:&amp;nbsp;장식자&amp;nbsp;추상&amp;nbsp;클래스 &lt;br /&gt;public&amp;nbsp;abstract&amp;nbsp;class&amp;nbsp;NotifierDecorator&amp;nbsp;implements&amp;nbsp;Notifier&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;Notifier&amp;nbsp;wrapper;&amp;nbsp;//&amp;nbsp;감싸고&amp;nbsp;있는&amp;nbsp;실제&amp;nbsp;대상&amp;nbsp;객체 &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;NotifierDecorator(Notifier&amp;nbsp;wrapper)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.wrapper&amp;nbsp;=&amp;nbsp;wrapper; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Override &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;send(String&amp;nbsp;message)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;wrapper.send(message);&amp;nbsp;//&amp;nbsp;기본&amp;nbsp;기능은&amp;nbsp;감싼&amp;nbsp;객체에게&amp;nbsp;위임 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;부가 기능 추가(ConcreteDecorator)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;//&amp;nbsp;4.&amp;nbsp;ConcreteDecorator:&amp;nbsp;SMS&amp;nbsp;알림&amp;nbsp;추가 &lt;br /&gt;public&amp;nbsp;class&amp;nbsp;SmsNotifier&amp;nbsp;extends&amp;nbsp;NotifierDecorator&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;SmsNotifier(Notifier&amp;nbsp;wrapper)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;super(wrapper); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Override &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;send(String&amp;nbsp;message)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;super.send(message);&amp;nbsp;//&amp;nbsp;내부에&amp;nbsp;감싼&amp;nbsp;객체의&amp;nbsp;알림&amp;nbsp;먼저&amp;nbsp;실행 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&quot;SMS&amp;nbsp;전송:&amp;nbsp;&quot;&amp;nbsp;+&amp;nbsp;message);&amp;nbsp;//&amp;nbsp;나만의&amp;nbsp;부가&amp;nbsp;기능&amp;nbsp;실행 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;//&amp;nbsp;4.&amp;nbsp;ConcreteDecorator:&amp;nbsp;Slack&amp;nbsp;알림&amp;nbsp;추가 &lt;br /&gt;public&amp;nbsp;class&amp;nbsp;SlackNotifier&amp;nbsp;extends&amp;nbsp;NotifierDecorator&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;SlackNotifier(Notifier&amp;nbsp;wrapper)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;super(wrapper); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Override &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;send(String&amp;nbsp;message)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;super.send(message); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&quot;Slack&amp;nbsp;메신저&amp;nbsp;전송:&amp;nbsp;&quot;&amp;nbsp;+&amp;nbsp;message); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;클라이언트 코드 실행&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트는 런타임에 원하는 알림 방식만 레고 블록 조립하듯 동적으로 연결하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public&amp;nbsp;class&amp;nbsp;NotificationSystem&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;static&amp;nbsp;void&amp;nbsp;main(String[]&amp;nbsp;args)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;상황&amp;nbsp;1.&amp;nbsp;이메일만&amp;nbsp;보내기 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Notifier&amp;nbsp;simpleNotifier&amp;nbsp;=&amp;nbsp;new&amp;nbsp;BasicNotifier(); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;simpleNotifier.send(&quot;서버&amp;nbsp;점검&amp;nbsp;안내&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;출력:&amp;nbsp;이메일&amp;nbsp;전송:&amp;nbsp;서버&amp;nbsp;점검&amp;nbsp;안내 &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&quot;-----------------&quot;); &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;상황&amp;nbsp;2.&amp;nbsp;이메일&amp;nbsp;+&amp;nbsp;SMS&amp;nbsp;+&amp;nbsp;Slack&amp;nbsp;모두&amp;nbsp;보내기&amp;nbsp;(동적&amp;nbsp;조합) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;객체가&amp;nbsp;객체를&amp;nbsp;계속해서&amp;nbsp;감싸는&amp;nbsp;구조 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Notifier&amp;nbsp;complexNotifier&amp;nbsp;=&amp;nbsp;new&amp;nbsp;SlackNotifier( &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;new&amp;nbsp;SmsNotifier( &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;new&amp;nbsp;BasicNotifier())); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;complexNotifier.send(&quot;장애&amp;nbsp;발생&amp;nbsp;경고!&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;출력: &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;이메일&amp;nbsp;전송:&amp;nbsp;장애&amp;nbsp;발생&amp;nbsp;경고! &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;SMS&amp;nbsp;전송:&amp;nbsp;장애&amp;nbsp;발생&amp;nbsp;경고! &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Slack&amp;nbsp;메신저&amp;nbsp;전송:&amp;nbsp;장애&amp;nbsp;발생&amp;nbsp;경고! &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데코레이터 패턴의 가장 큰 장점은 &lt;b&gt;OCP(개방-폐쇄 원칙)&lt;/b&gt;를 완벽하게 준수한다는 점이다. 기존 코드(BasicNotifier, NotifierDecorator)를 단 한줄도 수정하지 않고, 새로운 장식자 클래스만 추가하면 기능을 무한히 조합하고 확장할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;활용&lt;/b&gt; : Java의 I/O 스트림 (BufferedReader(new FileReader(...))), Spring 프레임워크의 HTTP Request/Response Wrapper 객체 처리 등에서 널리 쓰인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;주의점&lt;/b&gt; : 장식자를 과도하게 사용하면 자잘한 클래스가 너무 많아지고, 초기화 코드가 길어져 (new A(new B(new C())) 가독성이 떨어질 수 있다. 이때는 팩토리(Factory)나 빌더(Builder) 패턴을 조합해 생성 과정을 깔끔하게 캡슐화하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능의 조합이 다양하게 발생할 여지가 있다면, 무턱대고 클래스 상속 트리부터 그리기 전에 객체를 겹겹이 감싸서 해결할 수 있을지 고민해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Programming/Design Pattern</category>
      <category>DecoratorPattern</category>
      <category>DesignPattern</category>
      <category>Java</category>
      <category>OOP</category>
      <category>객체지향</category>
      <category>데코레이터패턴</category>
      <category>디자인패턴</category>
      <category>리팩토링</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/251</guid>
      <comments>https://hsunnystory.tistory.com/251#entry251comment</comments>
      <pubDate>Thu, 5 Mar 2026 13:47:05 +0900</pubDate>
    </item>
    <item>
      <title>[OOP] 다형성 리팩토링 : 인터페이스(Interface) vs 추상 클래스 (Abstract) 완벽 비교</title>
      <link>https://hsunnystory.tistory.com/250</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이전 포스팅에서 다형성(Polymorphism)을 활용해 지긋지긋한 if-else 분기문을 걷어내는 리팩토링 과정을 살펴보았다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://hsunnystory.tistory.com/235&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://hsunnystory.tistory.com/235&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772507003015&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Design Pattern] 다형성(Polymorphism)으로 if-else 없애기&quot; data-og-description=&quot;코드를 짜다 보면 기능은 정상적으로 동작하지만, 뭔가 지저분하고 유지보수하기 힘든 코드를 마주하게 된다. 이를 코드 스멜(Code Smell)이라고 한다. 대표적인 코드 스멜 중 하나가 객체의 타입&quot; data-og-host=&quot;hsunnystory.tistory.com&quot; data-og-source-url=&quot;https://hsunnystory.tistory.com/235&quot; data-og-url=&quot;https://hsunnystory.tistory.com/235&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bkxFkE/dJMb83kqudg/hbKZrOZ2fwbvqwDEXAWSNK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/e5hNl/dJMb86OZnw7/kWV4JUGD9kWm5pw3CNNq20/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://hsunnystory.tistory.com/235&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hsunnystory.tistory.com/235&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bkxFkE/dJMb83kqudg/hbKZrOZ2fwbvqwDEXAWSNK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/e5hNl/dJMb86OZnw7/kWV4JUGD9kWm5pw3CNNq20/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Design Pattern] 다형성(Polymorphism)으로 if-else 없애기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;코드를 짜다 보면 기능은 정상적으로 동작하지만, 뭔가 지저분하고 유지보수하기 힘든 코드를 마주하게 된다. 이를 코드 스멜(Code Smell)이라고 한다. 대표적인 코드 스멜 중 하나가 객체의 타입&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hsunnystory.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 여기서 생기는 의문점이 하나 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;다형성을 위한 부모 타입으로 interface를 쓰는 게 좋을까? 아니면 abstract class를 쓰는게 좋을까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론부터 말하자면 두 가지 모두 다형성을 구현할 수 있지만, &lt;b&gt;객체 지향 설계의 관점(IS-A vs CAN-DO)과 코드의 재사용성&lt;/b&gt; 측면에서 명확히 쓰임새가 다르다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 선택의 핵심 기준 : 상태(Stace)와 IS-A 관계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 먼저 따져야할 것은 &lt;b&gt;하위 클래스들이 공통적인 상태(필드 변수)와 기본 로직을 공유하는가&lt;/b&gt; 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 추상 클래스 (Abstract Class) : 하위 클래스들과 &lt;b&gt;IS-A (이것은 ~이다)&lt;/b&gt; 관계로 강하게 묶일 때 사용한다. 자식들이 공통으로 가져야 할 &lt;b&gt;상태(멤버 변수)와 중복되는 구현 코드&lt;/b&gt;를 부모가 제공해야 할 때 적합하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 인터페이스 (Interface) : 클래스의 종류와 상관 없이 &lt;b&gt;CAN-DO (~을 할 수 있다)&lt;/b&gt; 관점에서 &lt;b&gt;동일한 행위(메서드 규칙)&lt;/b&gt;만 보장하면 될 때 사용한다. 인터페이스는 원칙적으로 상태(인스턴스 변수)를 가질 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Interface가 더 좋은 경우 (행위 중심의 다형성)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 if-else 분기문을 없애고 특정 로직(알고리즘)만 상황에 따라 갈아끼우는 목적이라면, &lt;b&gt;인터페이스가 압도적으로 유리하다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바는 다중 상속을 지원하지 않기 때문에, 추상 클래스를 부모로 선택하면 하위 클래스는 더 이상 다른 클래스를 상속받을 수 없는 제약에 걸린다. 반면 인터페이스는 구현(implements)의 개수 제한이 없으므로 확장에 훨씬 유연하다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Good Code : 인터페이스를 활용한 결제 로직 다형성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;//&amp;nbsp;결제라는&amp;nbsp;'행위'만&amp;nbsp;규정하므로&amp;nbsp;인터페이스가&amp;nbsp;적합하다.&amp;nbsp;(상태&amp;nbsp;공유&amp;nbsp;불필요) &lt;br /&gt;public&amp;nbsp;interface&amp;nbsp;PaymentStrategy&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;void&amp;nbsp;pay(int&amp;nbsp;amount); &lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;//&amp;nbsp;각&amp;nbsp;결제&amp;nbsp;수단은&amp;nbsp;각자의&amp;nbsp;로직만&amp;nbsp;책임진다. &lt;br /&gt;public&amp;nbsp;class&amp;nbsp;KakaoPay&amp;nbsp;implements&amp;nbsp;PaymentStrategy&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Override &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;pay(int&amp;nbsp;amount)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&quot;카카오페이로&amp;nbsp;&quot;&amp;nbsp;+&amp;nbsp;amount&amp;nbsp;+&amp;nbsp;&quot;원&amp;nbsp;결제&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;public&amp;nbsp;class&amp;nbsp;CreditCardPay&amp;nbsp;implements&amp;nbsp;PaymentStrategy&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Override &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;pay(int&amp;nbsp;amount)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&quot;신용카드로&amp;nbsp;&quot;&amp;nbsp;+&amp;nbsp;amount&amp;nbsp;+&amp;nbsp;&quot;원&amp;nbsp;결제&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;//&amp;nbsp;클라이언트&amp;nbsp;(if-else&amp;nbsp;분기문&amp;nbsp;없이&amp;nbsp;다형성으로&amp;nbsp;처리) &lt;br /&gt;public&amp;nbsp;class&amp;nbsp;OrderService&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;런타임에&amp;nbsp;외부에서&amp;nbsp;주입받은&amp;nbsp;전략(인터페이스)만&amp;nbsp;실행하면&amp;nbsp;된다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;processPayment(PaymentStrategy&amp;nbsp;strategy,&amp;nbsp;int&amp;nbsp;amount)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;strategy.pay(amount);&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. Abstract Class가 더 좋은 경우 (상태와 공통 기능 공유)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 객체가 고유의 생명력(hp, name 등)을 가지는 게임 캐릭터 시스템을 생각해보자. 모든 캐릭터는 이러한 &lt;b&gt;상태&lt;/b&gt;를 가져야 하고, move() 같은 &lt;b&gt;공통 로직&lt;/b&gt;은 중복해서 작성할 필요가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 때는 인터페이스만으로 해결하기 어렵다. 필드 변수를 가질 수 없고, 공통 구현을 강제하면서 재사용 하기에는 한계가 있기 때문이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Good Code : 추상 클래스를 활용한 상태/행위 공유&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public&amp;nbsp;abstract&amp;nbsp;class&amp;nbsp;Character&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;1.&amp;nbsp;공통&amp;nbsp;상태&amp;nbsp;(인터페이스에서는&amp;nbsp;불가능한&amp;nbsp;인스턴스&amp;nbsp;변수) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;protected&amp;nbsp;String&amp;nbsp;name; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;protected&amp;nbsp;int&amp;nbsp;hp; &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;Character(String&amp;nbsp;name,&amp;nbsp;int&amp;nbsp;hp)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.name&amp;nbsp;=&amp;nbsp;name; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.hp&amp;nbsp;=&amp;nbsp;hp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;2.&amp;nbsp;공통&amp;nbsp;로직&amp;nbsp;(코드&amp;nbsp;중복&amp;nbsp;방지) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;move()&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(this.name&amp;nbsp;+&amp;nbsp;&quot;가(이)&amp;nbsp;이동합니다.&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;3.&amp;nbsp;다형성을&amp;nbsp;위한&amp;nbsp;추상&amp;nbsp;메서드&amp;nbsp;(자식이&amp;nbsp;반드시&amp;nbsp;자신만의&amp;nbsp;방식으로&amp;nbsp;구현) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;abstract&amp;nbsp;void&amp;nbsp;attack(); &lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;public&amp;nbsp;class&amp;nbsp;Warrior&amp;nbsp;extends&amp;nbsp;Character&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;Warrior(String&amp;nbsp;name,&amp;nbsp;int&amp;nbsp;hp)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;super(name,&amp;nbsp;hp);&amp;nbsp;//&amp;nbsp;부모의&amp;nbsp;상태&amp;nbsp;초기화&amp;nbsp;활용 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Override &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;attack()&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&quot;대검&amp;nbsp;휘두르기!&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서는 OCP(개방-폐쇄 원칙)를 준수하면서도 코드 중복을 막기 위해 &lt;b&gt;인터페이스와 추상 클래스를 조합&lt;/b&gt;해서 사용하는 경우가 가장 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;a. 최상위에는 interface를 두어 &lt;b&gt;다형성과 유연성&lt;/b&gt;을 확보한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;b. 인터페이스를 부분적으로 구현하는 abstract class를 중간에 두어 &lt;b&gt;공통 로직을 처리&lt;/b&gt;(중복 제거)한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;c. 구체 클래스는 추상 클래스를 상속받아 &lt;b&gt;핵심 로직만 구현&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 컬렉션 프레임워크(List 인터페이스 -&amp;gt; AbstractList 추상 클래스 -&amp;gt; ArrayList 구체 클래스)가 바로 이 패턴으로 설계되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;if-else를 제거하기 위해 다형성을 적용할 때, 다음을 기억하자&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;기본 원칙&lt;/b&gt; : 유연성 확보를 위해 &lt;b&gt;인터페이스(Interface)를 우선적으로 고려&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;변경의 타이밍&lt;/b&gt; : 하위 객체들이 서로 공유해야 할 &lt;b&gt;데이터(상태)가 생기거나, 겹치는 핵심 로직이 발생&lt;/b&gt;하여 코드 중복이 심해진다면 그 때 &lt;b&gt;추상 클래스(Abstract Class)&lt;/b&gt;로 리팩토링하거나 골격 구현 패턴을 도입한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Programming/Skill</category>
      <category>abstractclass</category>
      <category>Interface</category>
      <category>OOP</category>
      <category>polymorphism</category>
      <category>Refactoring</category>
      <category>객체지향</category>
      <category>다형성</category>
      <category>인터페이스</category>
      <category>추상클래스</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/250</guid>
      <comments>https://hsunnystory.tistory.com/250#entry250comment</comments>
      <pubDate>Tue, 3 Mar 2026 12:20:04 +0900</pubDate>
    </item>
    <item>
      <title>[Refactoring] if-else 지옥에서 탈출하기 Abstract 심화편</title>
      <link>https://hsunnystory.tistory.com/249</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 요구사항이 올 때마다 else if를 추가하고 있다면 그 코드는 썩어가고 있다는 신호다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 abstract를 활용한 다형성(Polymorphism)으로 지저분한 분기문을 깔끔하게 리팩토링하는 과정에 대해 작성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 문제 상황 (Bad Code)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게임의 캐릭터 스킬 시스템을 구현한다고 가정해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;class&amp;nbsp;Character&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;type;&amp;nbsp;//&amp;nbsp;&quot;WARRIOR&quot;,&amp;nbsp;&quot;WIZARD&quot;,&amp;nbsp;&quot;ARCHER&quot; &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;void&amp;nbsp;attack()&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(type.equals(&quot;WARRIOR&quot;))&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&quot;대검&amp;nbsp;휘두르기!&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;전사만의&amp;nbsp;복잡한&amp;nbsp;데미지&amp;nbsp;공식... &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;else&amp;nbsp;if&amp;nbsp;(type.equals(&quot;WIZARD&quot;))&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&quot;파이어볼&amp;nbsp;발사!&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;마법사만의&amp;nbsp;마나&amp;nbsp;소모&amp;nbsp;로직... &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;else&amp;nbsp;if&amp;nbsp;(type.equals(&quot;ARCHER&quot;))&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&quot;활&amp;nbsp;쏘기!&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;궁수만의&amp;nbsp;화살&amp;nbsp;카운트&amp;nbsp;로직... &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;새로운&amp;nbsp;직업&amp;nbsp;&quot;THIEF&quot;가&amp;nbsp;추가되면?&amp;nbsp;-&amp;gt;&amp;nbsp;여기&amp;nbsp;또&amp;nbsp;수정해야&amp;nbsp;함&amp;nbsp;(OCP&amp;nbsp;위반) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드의 문제점은 &lt;b&gt;새로운 직업이 추가될 때마다 기존 코드를 수정해야 한다&lt;/b&gt;는 점이다. 실수로 else if를 잘못 건드리면 전체가 망가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 리팩토링 (Good Code)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공통점은 묶고(추상화), 차이점은 분리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Step 1. 추상 클래스 추출&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;abstract&amp;nbsp;class&amp;nbsp;Character&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;공통&amp;nbsp;속성 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;name; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;int&amp;nbsp;hp; &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;공통&amp;nbsp;기능 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;void&amp;nbsp;move()&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&quot;이동합니다.&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;변하는&amp;nbsp;부분&amp;nbsp;-&amp;gt;&amp;nbsp;추상&amp;nbsp;메서드로&amp;nbsp;위임 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;abstract&amp;nbsp;void&amp;nbsp;attack(); &lt;br /&gt;}&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Step 2. 각 직업별 클래스 구현&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;class&amp;nbsp;Warrior&amp;nbsp;extends&amp;nbsp;Character&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Override &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;void&amp;nbsp;attack()&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&quot;대검&amp;nbsp;휘두르기!&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;class&amp;nbsp;Wizard&amp;nbsp;extends&amp;nbsp;Character&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Override &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;void&amp;nbsp;attack()&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&quot;파이어볼&amp;nbsp;발사!&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Step 3. 클라이언트 코드 (다형성 활용)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public&amp;nbsp;class&amp;nbsp;Game&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;static&amp;nbsp;void&amp;nbsp;main(String[]&amp;nbsp;args)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;상황에&amp;nbsp;따라&amp;nbsp;캐릭터만&amp;nbsp;갈아끼우면&amp;nbsp;됨 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Character&amp;nbsp;myChar&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Warrior(); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myChar.attack();&amp;nbsp;//&amp;nbsp;대검&amp;nbsp;휘두르기! &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myChar&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Wizard(); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myChar.attack();&amp;nbsp;//&amp;nbsp;파이어볼&amp;nbsp;발사! &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리팩토링의 핵심은 &lt;b&gt;변하는 것과 변하지 않는 것을 분리하는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- move() 처럼 모든 캐릭터가 똑같이 하는 건 부모(추상 클래스)에 둔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- attack() 처럼 캐릭터마다 다른 건 자식에게 맡긴다. (abstract method)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 구조를 잡아두면, 나중에 Thief 클래스가 추가 되어도 Game 클래스나 Warrior 클래스는 건드릴 필요가 없다. 이것이 바로 객체 지향의 묘미다.&lt;/p&gt;</description>
      <category>Programming/Skill</category>
      <category>abstract</category>
      <category>cleancode</category>
      <category>Java</category>
      <category>polymorphism</category>
      <category>Refactoring</category>
      <category>다형성</category>
      <category>리팩토링</category>
      <category>클린코드</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/249</guid>
      <comments>https://hsunnystory.tistory.com/249#entry249comment</comments>
      <pubDate>Fri, 27 Feb 2026 10:19:42 +0900</pubDate>
    </item>
    <item>
      <title>[Design Pattern] 추상 클래스(Abstract Class) 활용편 - 템플릿 메서드 패턴(Template Method Pattern)</title>
      <link>https://hsunnystory.tistory.com/248</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;추상클래스(Abstract Class) 기본편(&lt;a href=&quot;https://hsunnystory.tistory.com/247&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://hsunnystory.tistory.com/247&lt;/a&gt;) 에서 개념을 익혔다면, 이제 실무에서 사용하는 방법인 &lt;b&gt;템플릿 메서드 패턴(Template Method Pattern)&lt;/b&gt;에 대해 알아보도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 템플릿 메서드 패턴(Template Method Pattern) 이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;전체적인 흐름(알고리즘의 뼈대)은 부모가 잡고, 세부적인 구현은 자식에게 맡기는 패턴&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 라면 끓이기를 보면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;a. 물을 끓인다. (공통)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;b. &lt;b&gt;면을 넣는다.&lt;/b&gt; (종류마다 다름: 굵은 면, 얇은 면)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;c. &lt;b&gt;스프를 넣는다.&lt;/b&gt; (종류마다 다름: 매운 맛, 안매운 맛)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;d. 기다린다 (공통)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 a,d는 부모(추상 클래스)가 정의하고, b,c는 자식(구현 클래스)에 채워 넣게 하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 코드 예제: 데이터 분석기(File vs DB)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 읽어서(read) -&amp;gt; 분석하고(analyze) -&amp;gt; 결과를 쓰는(write) 프로그램이 있다고 가정하자. 전체 흐름은 같지만, 리소스를 어디서 읽느냐에 따라 로직이 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Step 1. 추상 클래스 (템플릿 정의)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public&amp;nbsp;abstract&amp;nbsp;class&amp;nbsp;DataAnalyzer&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;템플릿&amp;nbsp;메서드:&amp;nbsp;전체&amp;nbsp;흐름을&amp;nbsp;조율한다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;final을&amp;nbsp;붙여서&amp;nbsp;자식이&amp;nbsp;흐름을&amp;nbsp;마음대로&amp;nbsp;바꾸지&amp;nbsp;못하게&amp;nbsp;막는다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;final&amp;nbsp;void&amp;nbsp;process()&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;readData();&amp;nbsp;//&amp;nbsp;자식이&amp;nbsp;구현 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;analyze();&amp;nbsp;&amp;nbsp;//&amp;nbsp;공통&amp;nbsp;로직 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;writeData();&amp;nbsp;//&amp;nbsp;자식이&amp;nbsp;구현 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;[추상&amp;nbsp;메서드]&amp;nbsp;자식이&amp;nbsp;반드시&amp;nbsp;구현해야&amp;nbsp;함&amp;nbsp;(Hook) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;abstract&amp;nbsp;void&amp;nbsp;readData(); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;abstract&amp;nbsp;void&amp;nbsp;writeData(); &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;[일반&amp;nbsp;메서드]&amp;nbsp;공통&amp;nbsp;로직 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;void&amp;nbsp;analyze()&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&quot;데이터를&amp;nbsp;분석합니다...&amp;nbsp;(공통&amp;nbsp;로직)&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Step 2. 구현 클래스&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public&amp;nbsp;class&amp;nbsp;FileDataAnalyzer&amp;nbsp;extends&amp;nbsp;DataAnalyzer&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Override &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;void&amp;nbsp;readData()&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&quot;파일에서&amp;nbsp;데이터를&amp;nbsp;읽어옵니다.&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Override &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;void&amp;nbsp;writeData()&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&quot;파일로&amp;nbsp;리포트를&amp;nbsp;저장합니다.&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;public&amp;nbsp;class&amp;nbsp;DbDataAnalyzer&amp;nbsp;extends&amp;nbsp;DataAnalyzer&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Override &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;void&amp;nbsp;readData()&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&quot;DB에서&amp;nbsp;데이터를&amp;nbsp;조회합니다.&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Override &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;void&amp;nbsp;writeData()&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&quot;DB&amp;nbsp;테이블에&amp;nbsp;결과를&amp;nbsp;적재합니다.&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Step 3. 실행 (Client)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public&amp;nbsp;class&amp;nbsp;Main&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;static&amp;nbsp;void&amp;nbsp;main(String[]&amp;nbsp;args)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;DataAnalyzer&amp;nbsp;analyzer&amp;nbsp;=&amp;nbsp;new&amp;nbsp;FileDataAnalyzer(); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;analyzer.process();&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;출력: &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;파일에서&amp;nbsp;데이터를&amp;nbsp;읽어옵니다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;데이터를&amp;nbsp;분석합니다...&amp;nbsp;(공통&amp;nbsp;로직) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;파일로&amp;nbsp;리포트를&amp;nbsp;저장합니다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 장점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;제어의 역전(IoC)&lt;/b&gt; : 자식 클래스가 흐름을 제어하는 것이 아니라, 부모 클래스가 흐름을 쥐고 자식의 코드를 호출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;코드 중복 최소화&lt;/b&gt; : 변하지 않는 부분(알고리즘 구조)을 한곳에 몰아 넣어 유지보수가 쉽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;실무에서 전체 로직은 비슷한데 특정 부분만 조금씩 다른 경우가 있다면, 주저 말고 abstract 클래스와 템플릿 메서드 패턴을 사용해보자&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>Programming/Design Pattern</category>
      <category>abstractclass</category>
      <category>DesignPattern</category>
      <category>Java</category>
      <category>OOP</category>
      <category>TemplateMethod</category>
      <category>객체지향</category>
      <category>디자인패턴</category>
      <category>추상클래스</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/248</guid>
      <comments>https://hsunnystory.tistory.com/248#entry248comment</comments>
      <pubDate>Thu, 26 Feb 2026 10:57:47 +0900</pubDate>
    </item>
    <item>
      <title>[Java] 추상 클래스(Abstract class) 란? (기본편)</title>
      <link>https://hsunnystory.tistory.com/247</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;신입 개발자 시절 가장 이해가 안 갔던 개념이 바로 추상 클래스(Abstract class) 였다. &quot;그냥 일반 클래스로 만들면 되지 왜 굳이 abstract를 붙여서 객체 생성도 못 하게 막는 이유가 뭘까?&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 추상 클래스란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;abstract 키워드가 붙은 클래스는 &lt;b&gt;미완성 설계도&lt;/b&gt;라고 보면 이해가 쉽다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설계도가 미완성이라서 이 클래스 자체로는 객체를 생성할 수 없다. 오직 &lt;b&gt;상속받은 자식 클래스를 통해서만&lt;/b&gt; 완성되고 사용될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;추상 메서드(abstract method)&lt;/b&gt; : 껍데기만 있고 구현부가 없는 메서드. 자식이 &lt;b&gt;반드시&lt;/b&gt; 구현해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;일반 메서드&lt;/b&gt; : 자식들이 공통으로 사용할 기능&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 사용 이유&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;강제성 부여 (규격의 통일)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식 클래스에게 메서드를 무조건 만들게 강제할 수 있다. 실수로 구현을 빼먹는 것을 방지한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;중복 코드 제거 (공통 기능 묶기)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 자식들이 똑같이 쓰는 기능은 부모(추상 클래스)에 구현해두고, 자식은 가져다 쓰기만 하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 예제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결제 수단은 여러가지(카드, 무통장, 포인트)가 있지만, &lt;b&gt;결제하기&lt;/b&gt;라는 핵심 동작은 같아야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[Bad Code]&lt;/b&gt; : 일반 클래스 상속&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;class&amp;nbsp;Payment&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;void&amp;nbsp;pay()&amp;nbsp;{&amp;nbsp;/*&amp;nbsp;아무것도&amp;nbsp;안&amp;nbsp;함?&amp;nbsp;혹은&amp;nbsp;기본&amp;nbsp;로직?&amp;nbsp;애매함&amp;nbsp;*/&amp;nbsp;} &lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;class&amp;nbsp;CardPayment&amp;nbsp;extends&amp;nbsp;Payment&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;개발자가&amp;nbsp;실수로&amp;nbsp;pay()&amp;nbsp;오버라이딩을&amp;nbsp;안&amp;nbsp;해도&amp;nbsp;에러가&amp;nbsp;안&amp;nbsp;난다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;결제&amp;nbsp;로직이&amp;nbsp;없는데&amp;nbsp;프로그램은&amp;nbsp;돌아가는&amp;nbsp;대참사&amp;nbsp;발생. &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[Good Code]&lt;/b&gt;&amp;nbsp;: 추상 클래스 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;//&amp;nbsp;1.&amp;nbsp;추상&amp;nbsp;클래스&amp;nbsp;선언 &lt;br /&gt;abstract&amp;nbsp;class&amp;nbsp;Payment&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;[공통&amp;nbsp;기능]&amp;nbsp;모든&amp;nbsp;결제&amp;nbsp;수단은&amp;nbsp;로깅이&amp;nbsp;필요하다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;logging()&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&quot;결제&amp;nbsp;로그를&amp;nbsp;남깁니다.&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;[추상&amp;nbsp;메서드]&amp;nbsp;결제&amp;nbsp;방식은&amp;nbsp;자식마다&amp;nbsp;다르므로,&amp;nbsp;구현을&amp;nbsp;강제한다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;abstract&amp;nbsp;void&amp;nbsp;processPay();&amp;nbsp; &lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;//&amp;nbsp;2.&amp;nbsp;자식&amp;nbsp;클래스&amp;nbsp;구현 &lt;br /&gt;class&amp;nbsp;CardPayment&amp;nbsp;extends&amp;nbsp;Payment&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Override &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;void&amp;nbsp;processPay()&amp;nbsp;{&amp;nbsp;//&amp;nbsp;이걸&amp;nbsp;구현&amp;nbsp;안&amp;nbsp;하면&amp;nbsp;컴파일&amp;nbsp;에러&amp;nbsp;발생!&amp;nbsp;(안전장치) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&quot;신용카드&amp;nbsp;결제&amp;nbsp;진행&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;class&amp;nbsp;CashPayment&amp;nbsp;extends&amp;nbsp;Payment&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Override &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;void&amp;nbsp;processPay()&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&quot;현금&amp;nbsp;결제&amp;nbsp;진행&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4 인터페이스(Interface) vs 추상 클래스(abstract class)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 추상 클래스 : IS-A 관계. 자신의 기능(일반 메서드/필드)을 물려주면서 기능을 확장하는 것이 목적, 상태(멤버 변수)를 가질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ex) Galaxy 25는 SmartPhone 이다. (부품 상태를 공유)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 인터페이스 : HAS-A/Can-do 관계. 서로 다른 클래스들이 같은 동작을 하도록 규약한 정하는 것이 목적&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EX) Galaxy 25와 Iphone는 둘다 CALL 할 수 있다.(전화 할 수 있다는 동작만 정의)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요약&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;공통된 필드와 메서드&lt;/b&gt;가 많고, &lt;b&gt;기본 구현&lt;/b&gt;을 제공하고 싶다면 abstract를 써라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 단순히 &lt;b&gt;동작의 규격&lt;/b&gt;만 맞추고 싶다면 interface를 써라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Programming/Java</category>
      <category>abstractclass</category>
      <category>Java</category>
      <category>OCP</category>
      <category>객체지향</category>
      <category>추상클래스</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/247</guid>
      <comments>https://hsunnystory.tistory.com/247#entry247comment</comments>
      <pubDate>Fri, 13 Feb 2026 15:00:15 +0900</pubDate>
    </item>
    <item>
      <title>[IntelliJ] 인텔리제이 단축키 모음(Windows)</title>
      <link>https://hsunnystory.tistory.com/246</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 만능 해결사(&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;필수&lt;/span&gt;&lt;/b&gt;)&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Alt + Enter (Show Context Actions)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 기능 : 에러 수정, import 추가, 코드 제안 등 모든 문제 해결&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 상황 : 빨간 줄이 있다? 무조건 눌러보자&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Shift + Shift (Search Everywhere)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 기능 : 파일, 클래스, 설정, 기능 까지 모든 검색&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 상황 : 특정 파일을 찾거나 설정 변경 등&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Ctrl + E (Recent Files)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 기능 : 최근 열었던 파일 목록&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 상황 : 상단 탭(Tab) 클릭하지 말자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 네비게이션&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 탐색기(Project View)를 마우스로 뒤적거리는 시간을 없애자&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Ctrl + N&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스 이름으로 찾기&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Ctrl + Shift + N&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 이름으로 찾기.(xml, properties 등)&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Ctrl + B&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선언부(Declaration)로 이동&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Ctrl + Alt + B&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;구현체(Implementation)로 이동.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Ctrl + F12&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 구조 보기. 현재 클래스 내의 모든 메서드와 필드를 리스트로 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 편집(작성)&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Ctrl + D&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;줄 복제 (Duplicate Line)&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Ctrl + Y&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;줄 삭제 (Delete Line)&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Alt + Shift + &amp;uarr; / &amp;darr; : 줄이동&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘라내기(Ctrl+X) -&amp;gt; 붙여넣기(Ctrl+V) 대신 줄 자체를 위아래로 이동&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Ctrl + Alt + L : &lt;b&gt;코드 포맷팅 (Reformat Code)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;들여쓰기 정렬&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 리팩토링(수정)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수명 일괄 변경&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Shift + F6 : 이름 변경(Rename)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수, 메서드, 클래스 이름을 안전하게 바꾼다. 참조하고 있는 모든 곳이 동시에 바뀐다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Ctrl + Alt + V : 변수 추출 (Extract Variable)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;ex) memberRepository.findByID(1L).get() 뒤에 커서 두고 누르면 Member member = ... 자동생성&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Ctrl + Alt + M : 메서드 추출 (Extract Method)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지저분한 로직을 드래그해서 누르면, 깔끔하게 메서드로 분리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 다중 커서&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 단어를 여러 개 동시에 수정하고 싶을 때 쓰는 기능&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Alt + J : 다음 같은 단어 선택&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수명에 커서를 두고 Alt + J 를 연타하면, 아래에 있는 변수들이 하나씩 추가로 잡힌다. 그 상태에서 타이핑하면 동시에 수정된다.&lt;/p&gt;</description>
      <category>Programming/Skill</category>
      <category>IDE</category>
      <category>IntelliJ</category>
      <category>단축키</category>
      <category>생산성</category>
      <category>인텔리제이</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/246</guid>
      <comments>https://hsunnystory.tistory.com/246#entry246comment</comments>
      <pubDate>Thu, 12 Feb 2026 21:39:26 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] 전략 패턴(Strategy Pattern) : Map 주입으로 if-else 제거하기 (OCP 준수)</title>
      <link>https://hsunnystory.tistory.com/245</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지난 포스팅에서 if-else 분기문이 가지는 유지보수의 문제점을 확인했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://hsunnystory.tistory.com/235&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://hsunnystory.tistory.com/235&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1770783421281&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Design Pattern] 다형성(Polymorphism)으로 if-else 없애기&quot; data-og-description=&quot;코드를 짜다 보면 기능은 정상적으로 동작하지만, 뭔가 지저분하고 유지보수하기 힘든 코드를 마주하게 된다. 이를 코드 스멜(Code Smell)이라고 한다. 대표적인 코드 스멜 중 하나가 객체의 타입&quot; data-og-host=&quot;hsunnystory.tistory.com&quot; data-og-source-url=&quot;https://hsunnystory.tistory.com/235&quot; data-og-url=&quot;https://hsunnystory.tistory.com/235&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eb5bZf/dJMb85WPdif/S2TklPAa0t55RH7rkfDg0K/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/ZtzwH/dJMb86nTtde/8B5VshQUISOmNay7tTrdGK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://hsunnystory.tistory.com/235&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hsunnystory.tistory.com/235&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eb5bZf/dJMb85WPdif/S2TklPAa0t55RH7rkfDg0K/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/ZtzwH/dJMb86nTtde/8B5VshQUISOmNay7tTrdGK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Design Pattern] 다형성(Polymorphism)으로 if-else 없애기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;코드를 짜다 보면 기능은 정상적으로 동작하지만, 뭔가 지저분하고 유지보수하기 힘든 코드를 마주하게 된다. 이를 코드 스멜(Code Smell)이라고 한다. 대표적인 코드 스멜 중 하나가 객체의 타입&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hsunnystory.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 스프링 컨터이너의 강력한 기능인 &lt;b&gt;Map 주입&lt;/b&gt;을 활용해보고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 스프링의 Map 주입 기능&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링은 필드 타입을 Map&amp;lt;String, Interface&amp;gt;로 선언하면, 해당 인터페이스를 구현한 &lt;b&gt;모든 빈(Bean)&lt;/b&gt;을 찾아서 맵에 담아준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 &lt;b&gt;Key는 빈 이름(Bean Name)&lt;/b&gt;, &lt;b&gt;Value는&lt;/b&gt; &lt;b&gt;빈(Bean) 객체&lt;/b&gt;가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 구현 코드(Refactoring)&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Step 1. 빈 이름 지정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명확한 매핑을 위해 @Component에 이름을 지정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Component(&quot;NAVER&quot;)&amp;nbsp;//&amp;nbsp;빈&amp;nbsp;이름&amp;nbsp;지정 &lt;br /&gt;public&amp;nbsp;class&amp;nbsp;NaverPay&amp;nbsp;implements&amp;nbsp;Payment&amp;nbsp;{&amp;nbsp;...&amp;nbsp;} &lt;br /&gt;&lt;br /&gt;@Component(&quot;KAKAO&quot;) &lt;br /&gt;public&amp;nbsp;class&amp;nbsp;KakaoPay&amp;nbsp;implements&amp;nbsp;Payment&amp;nbsp;{&amp;nbsp;...&amp;nbsp;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Step 2. Service 리팩토링&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Service &lt;br /&gt;@RequiredArgsConstructor &lt;br /&gt;public&amp;nbsp;class&amp;nbsp;PaymentService&amp;nbsp;{ &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;[핵심]&amp;nbsp;모든&amp;nbsp;구현체가&amp;nbsp;여기로&amp;nbsp;들어옴 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;{&quot;NAVER&quot;:&amp;nbsp;naverPay객체,&amp;nbsp;&quot;KAKAO&quot;:&amp;nbsp;kakaoPay객체} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;Map&amp;lt;String,&amp;nbsp;Payment&amp;gt;&amp;nbsp;paymentMap; &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;pay(String&amp;nbsp;payMethod,&amp;nbsp;int&amp;nbsp;amount)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;1.&amp;nbsp;맵에서&amp;nbsp;꺼내기&amp;nbsp;(O(1)) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Payment&amp;nbsp;payment&amp;nbsp;=&amp;nbsp;paymentMap.get(payMethod); &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;2.&amp;nbsp;예외&amp;nbsp;처리 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(payment&amp;nbsp;==&amp;nbsp;null)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw&amp;nbsp;new&amp;nbsp;IllegalArgumentException(&quot;지원하지&amp;nbsp;않는&amp;nbsp;결제&amp;nbsp;방식&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;3.&amp;nbsp;실행 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;payment.pay(amount); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 TossPay가 추가되어도 PaymentService는 수정할 필요가 없다. 그저 @Component(&quot;TOSS&quot;)가 붙은 클래스만 하나 만들면 끝이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 문자열(String) 대신 Enum 사용하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NAVER, KAKAO 같은 문자열을 그대로 쓰는 것은 오타 위험이 있다. Enum을 활용하면 타입 안정성 까지 챙길 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Getter &lt;br /&gt;@RequiredArgsConstructor &lt;br /&gt;public&amp;nbsp;enum&amp;nbsp;PayType&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;NAVER(&quot;NAVER&quot;), &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;KAKAO(&quot;KAKAO&quot;); &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;String&amp;nbsp;beanName;&amp;nbsp;//&amp;nbsp;빈&amp;nbsp;이름을&amp;nbsp;필드로&amp;nbsp;가짐 &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;//&amp;nbsp;Controller나&amp;nbsp;Service&amp;nbsp;호출&amp;nbsp;부 &lt;br /&gt;public&amp;nbsp;void&amp;nbsp;pay(PayType&amp;nbsp;payType,&amp;nbsp;int&amp;nbsp;amount)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Enum에서&amp;nbsp;빈&amp;nbsp;이름을&amp;nbsp;꺼내서&amp;nbsp;Map&amp;nbsp;조회 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Payment&amp;nbsp;payment&amp;nbsp;=&amp;nbsp;paymentMap.get(payType.getBeanName()); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;payment.pay(amount); &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링의&lt;b&gt; IoC 컨터이너&lt;/b&gt;는 단순히 객체를 만들어주는 것을 넘어, 다형성을 극대화할 수 있는 도구들을 제공한다. If-else 가 자꾸 늘어난다면, 혹은 switch 문이 보인다면 Map 주입을 쓸 수 없을지 고민해보자.&lt;/p&gt;</description>
      <category>Web Programming/Spring</category>
      <category>DI</category>
      <category>Map주입</category>
      <category>Spring</category>
      <category>SpringBoot</category>
      <category>SpringFramework</category>
      <category>Strategy Pattern</category>
      <category>의존성주입</category>
      <category>전략패턴</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/245</guid>
      <comments>https://hsunnystory.tistory.com/245#entry245comment</comments>
      <pubDate>Wed, 11 Feb 2026 13:23:55 +0900</pubDate>
    </item>
    <item>
      <title>슈도코드(Pseudocode) 작성법 및 예시</title>
      <link>https://hsunnystory.tistory.com/244</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;개발을 하다 보면 키보드에 손부터 올리는 경우가 많다. 하지만 복잡한 알고리즘 문제를 풀거나 새로운 기능을 개발할 때, 무작정 코드부터 치기 시작하면 중간에 논리가 꼬여 싹 다 지우는 경험을 하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 필요한 것이 바로 &lt;b&gt;슈도코드(Pseudocode)&lt;/b&gt;다. 건축으로 치면 설계도에 해당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 슈도코드(Psudocode)란&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가짜를 뜻하는 &lt;b&gt;Pseudo&lt;/b&gt;와 &lt;b&gt;Code&lt;/b&gt;의 합성어로 컴퓨터가 아닌 &lt;b&gt;사람이 읽기 위해 작성한 코드&lt;/b&gt;를 말한다. 특정 프로그래밍 언어의 문법에 얽매이지 않고, 프로그램의 논리적 흐름을 일반적인 언어와 코딩 스타일을 섞어 흉내 낸 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 필요성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;언어 독립성&lt;/b&gt; : Java 개발자와 C개발자가 서로 코드를 몰라도 슈도코드로 대화하면 로직을 이해할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;논리 집중&lt;/b&gt; : 세미콜론 빼먹었는지, 괄호가 맞는지 신경 쓸 필요 없이 어떻게 해결할까에만 집중할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;수정 용이&lt;/b&gt; : 코드를 다 짜고 나서 구조를 바꾸려면 대공사지만, 슈도코드는 그냥 지우개로 지우면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 작성 규칙&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슈도코드는 문법이 정해져 있지 않다. 다른사람이 알아볼 수만 있으면 된다. 하지만 통용되는 관례는 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;키워드는 대문자로&lt;/b&gt; : IF, ELSE, WHILE, FOR, RETURN, PRINT 등 명령어는 대문자로 써서 눈에 띄게 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;들여쓰기&lt;/b&gt; : 계층 구조를 파악하기 위해 들여쓰기는 필수다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;간결함&lt;/b&gt; : int a = 0; 처럼 구체적으로 쓰기 보단 set a to 0 처럼 쓴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 예시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[슈도 코드]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FUNCTION&amp;nbsp;FindMax(array) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Set&amp;nbsp;max_value&amp;nbsp;to&amp;nbsp;array[0] &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;FOR&amp;nbsp;each&amp;nbsp;number&amp;nbsp;IN&amp;nbsp;array &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;IF&amp;nbsp;number&amp;nbsp;&amp;gt;&amp;nbsp;max_value&amp;nbsp;THEN &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Set&amp;nbsp;max_value&amp;nbsp;to&amp;nbsp;number &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;END&amp;nbsp;IF &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;END&amp;nbsp;FOR &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;RETURN&amp;nbsp;max_value &lt;br /&gt;END&amp;nbsp;FUNCTION&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[실제 코드]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public&amp;nbsp;int&amp;nbsp;findMax(int[]&amp;nbsp;array)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;int&amp;nbsp;maxValue&amp;nbsp;=&amp;nbsp;array[0]; &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;(int&amp;nbsp;number&amp;nbsp;:&amp;nbsp;array)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(number&amp;nbsp;&amp;gt;&amp;nbsp;maxValue)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;maxValue&amp;nbsp;=&amp;nbsp;number; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;maxValue; &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 작성 팁&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;너무 자세히 쓰지 마라&lt;/b&gt; : i++, System.out.println 같이 특정 언어의 문법을 너무 많이 섞으면 슈도코드의 장점이 사라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;너무 추상적으로 쓰지 마라&lt;/b&gt; : 단순히 &lt;b&gt;정렬한다&lt;/b&gt;라고 쓰면 안된다. &lt;b&gt;버블 정렬을 수행한다&lt;/b&gt; 처럼 구현 가능한 수준이어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;한글로 써도 된다&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약(IF)&amp;nbsp;점수가&amp;nbsp;60점&amp;nbsp;이상이면 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;합격&quot;&amp;nbsp;출력 &lt;br /&gt;아니면(ELSE) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;불합격&quot;&amp;nbsp;출력&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각할 시간이 부족해서 코딩부터 한다는 말은 틀렸다. 생각을 정리하지 않고 코딩하면 디버깅하는 데 시간이 배로 든다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복잡한 문제를 만났다면, IDE가 아니라 메모장을 먼저 켜는 습관을 들이자&lt;/p&gt;</description>
      <category>Programming/Skill</category>
      <category>PS</category>
      <category>PseudoCode</category>
      <category>개발방법론</category>
      <category>슈도코드</category>
      <category>의사코드</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/244</guid>
      <comments>https://hsunnystory.tistory.com/244#entry244comment</comments>
      <pubDate>Tue, 10 Feb 2026 10:42:20 +0900</pubDate>
    </item>
    <item>
      <title>[IntelliJ] 인텔리제이 꿀팁 모음(HTTP Client, 단축키, 디버깅 등)</title>
      <link>https://hsunnystory.tistory.com/243</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;개발자라면 누구나 한 번쯤 &quot;나는 이 비싼 IDE를 메모장처럼 쓰고 있는건 아닐까&quot; 라는 의문을 가져본 적이 있을 것이다. 특히 Ctrl+C, Ctrl+V 와 기본적인 빌드만 하고 있다면, 인텔리제이가 가진 강력한 무기의 10%도 못 쓰고 있는 셈이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Postman이 필요 없다 : .http (HTTP Client)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인텔리제이에는 강력한 &lt;b&gt;HTTP Client&lt;/b&gt;가 내장되어 있다. Postman 같은 무거운 툴을 켤 필요 없이, 프로젝트 내에서 API 명세서처럼 관리하고 테스트할 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;사용법&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 내에 확장자가 .http인 파일을 생성한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;장점&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- Git으로 버전 관리가 가능하다. (팀원과 API 테스트 케이스 공유 가능)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 환경 변수(http-client.dnv.json)를 설정해 로컬/개발/운영 서버를 쉽게 오가며 테스트 할 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;EX)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP &lt;br /&gt;###&amp;nbsp;1.&amp;nbsp;유저&amp;nbsp;조회&amp;nbsp;API&amp;nbsp;테스트 &lt;br /&gt;GET&amp;nbsp;http://localhost:8080/api/users/1 &lt;br /&gt;Accept:&amp;nbsp;application/json &lt;br /&gt;&lt;br /&gt;###&amp;nbsp;2.&amp;nbsp;유저&amp;nbsp;생성&amp;nbsp;(JSON&amp;nbsp;Body) &lt;br /&gt;POST&amp;nbsp;http://localhost:8080/api/users &lt;br /&gt;Content-Type:&amp;nbsp;application/json &lt;br /&gt;&lt;br /&gt;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&quot;name&quot;:&amp;nbsp;&quot;Seon&quot;, &lt;br /&gt;&amp;nbsp;&amp;nbsp;&quot;age&quot;:&amp;nbsp;30 &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작성 후 라인 옆의 초록색 &lt;span style=&quot;color: #409d00;&quot;&gt;▶&lt;/span&gt; 버튼만 누르면 바로 요청이 전송되고 결과가 하단에 노출된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Git보다 강력한 보험 : Local History&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가끔 코드를 수정하다가 Ctrl+Z 로도 복구가 안되고, 아직 Commit도 안해서 망연자실한 적이 있을것이다. 이때 Local History를 쓰면 된다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기능&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인텔리 제이는 파일의 변경 사항을 자체적으로 계속 기록하고 있음 (Git 커밋 여부와 상관없음)&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;사용법&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트나 파일 우클릭 -&amp;gt; Local History -&amp;gt; Show History&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;활용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실수로 지운 파일 복구, 30분 전 코드로 롤백 등이 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 필수 단축키&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;영역 확장/축소&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 단축키 : Ctrl + W &lt;b&gt;(확장)&lt;/b&gt; / Ctrl + Shift + W &lt;b&gt;(축소)&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;- 설명 : 커서가 위치한 단어 -&amp;gt; 문장 -&amp;gt; 블록 -&amp;gt; 메서드 전체 순으로 선택영역이 스마트하게 확장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 활용 : if 문 전체를 복사하거나, 메서드 본문만 긁어올 때 사용&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;클립보드 히스토리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 단축키 : Ctrl + Shift + V&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 설명 : 방금 복사한 거 말고, &lt;b&gt;아까 복사했던 그 코드&lt;/b&gt;가 필요할 때가 있다. 인텔리제이는 과거의 복사 기록(기본 5개 이상)을 기억하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 활용 : 여러 곳에서 변수명을 복사해 두고, 순서대로 붙여넣기 할 때 유용하다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;줄 이동 및 복제&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 이동 : Alt + Shift + &amp;uarr;,&amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 복제 : Ctrl + D&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 설명 : 코드를 잘라내기(Ctrl+X)해서 붙여넣기(Ctrl+V) 하지 말고, 그냥 그 라인을 잡고 위아래로 옮기면 된다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;최근 수정 위치로 이동&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 단축키 : Ctrl + Shift + Backspace&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 설명 : 다른 파일 보고 오느라 스크롤을 이동했다가, &quot;방금 내가 짜던 코드가 어디지?&quot; 하고 헤맬 때 사용&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;구현체로 바로이동&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 단축키 : Ctrl + Alt + B&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 설명 : Interface 메서드 위에서 크냥 클릭(Ctrl+B)하면 인터페이스 정의로만 간다. 실제 구현 클래스(Impl)가 궁금할 때 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 타이핑 속도 개선 : Postfix Completion (후위 자동완성)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수를 먼저 치고 . 을 찍어서 문장을 완성하는 기능이다. 커서를 앞으로 옮겨서 if(...)를 감싸는 수고를 덜어준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 변수.sout -&amp;gt; System.out.printlf(변수);&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 변수.var -&amp;gt; String name = 변수; (&lt;b&gt;타입 추론하여 변수 선언&lt;/b&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 변수.nn -&amp;gt; if(변수 !- null) {...} (&lt;b&gt;null 체크&lt;/b&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 리스트.for -&amp;gt; &lt;b&gt;향상된 for문 자동 생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 서버 로그 분석 : Analyze Stack Trace&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기능&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Analyze -&amp;gt; Analyze Stack Trace or Thread Dump&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;사용법&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 서버 로그의 Exception 스택 트레이스 부분을 복사한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 인텔리제이로 돌아와서 이 메뉴를 실행하면, 자동으로 클립보드 내용을 읽어온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 로그의 파일명들이 하이퍼링크로 변한다. 클리하면 바로 내 프로젝트의 해당 라인으로 이동한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;효과&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버그 추적 속도가 10배 빨라진다.&lt;/p&gt;</description>
      <category>Programming/Skill</category>
      <category>HttpClient</category>
      <category>IDE</category>
      <category>IntelliJ</category>
      <category>LocalHistory</category>
      <category>단축키</category>
      <category>디버깅</category>
      <category>생산성</category>
      <category>인텔리제이</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/243</guid>
      <comments>https://hsunnystory.tistory.com/243#entry243comment</comments>
      <pubDate>Mon, 9 Feb 2026 14:54:17 +0900</pubDate>
    </item>
    <item>
      <title>조합(HAS-A)과 전략 패턴(Strategy Pattern)</title>
      <link>https://hsunnystory.tistory.com/242</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지난 포스팅에서 상속(IS-A)이 가진 경직성과 위험성에 대해 알아보았다. 객체지향 설계의 격언 중 &lt;b&gt;상속보다는 조합을 선호하라 (Favor Composition over Inheritance)&lt;/b&gt;라는 말이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 조합(HAS-A) 관계를 활용해, 실행 중에 기능을 마음대로 교체할 수 있는 유연한 코드를 작성하는 방법에 대해 살펴본다. 이것이 전략 패턴(Strategy Pattern)의 기초다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 조합(HAS-A)를 선호해야하는 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상속은 &lt;b&gt;태어날 때부터 정해진 운명&lt;/b&gt;이라면, 조합은 &lt;b&gt;장착할 수 있는 아이템&lt;/b&gt;과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 느슨한 결합 : 부모 클래스 코드를 몰라도 인터페이스만 알면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 런타임 유연성 : 프로그램 실행 도중에 객체(부품)을 교체할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 다중 상속의 대안 : 여러 개의 객체를 필드로 가지면, 여러 기능을 동시에 가질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 실전 예제 : 로봇과 무기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로봇이 공격 기능을 가져야 한다고 가정해보자. 상속을 쓰면 PunchRobot, MissileRobot 등 클래스가 무한히 늘어난다. 하지만 &lt;b&gt;HAS-A&lt;/b&gt;를 쓰면 로봇은 Weapon만 가지고 있으면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Step 1. 부품(Interface) 정의&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;interface&amp;nbsp;Weapon&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;void&amp;nbsp;attack(); &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Step 2. 부품 구현체(전략)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;class&amp;nbsp;Punch&amp;nbsp;implements&amp;nbsp;Weapon&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;attack()&amp;nbsp;{&amp;nbsp;System.out.println(&quot;주먹&amp;nbsp;발사!&quot;);&amp;nbsp;} &lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;class&amp;nbsp;Missile&amp;nbsp;implements&amp;nbsp;Weapon&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;attack()&amp;nbsp;{&amp;nbsp;System.out.println(&quot;미사일&amp;nbsp;발사!&quot;);&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Step 3. 로봇 클래스(조합 활용)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;class&amp;nbsp;Robot&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;Weapon&amp;nbsp;weapon;&amp;nbsp;//&amp;nbsp;HAS-A&amp;nbsp;:&amp;nbsp;로봇은&amp;nbsp;무기를&amp;nbsp;가지고&amp;nbsp;있다. &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;생성자로&amp;nbsp;무기&amp;nbsp;장착 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;Robot(Weapon&amp;nbsp;weapon)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.weapon&amp;nbsp;=&amp;nbsp;weapon; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;[핵심]&amp;nbsp;런타임에&amp;nbsp;무기&amp;nbsp;교체&amp;nbsp;가능&amp;nbsp;(Setter) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;setWeapon(Weapon&amp;nbsp;weapon)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.weapon&amp;nbsp;=&amp;nbsp;weapon; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;fight()&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;weapon.attack();&amp;nbsp;//&amp;nbsp;기능을&amp;nbsp;위임(Delegation) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 유연함의 증명&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로봇 코드를 전혀 수정하지 않고도, 실행 중에 공격 방식을 바꿀 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public&amp;nbsp;static&amp;nbsp;void&amp;nbsp;main(String[]&amp;nbsp;args)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Robot&amp;nbsp;taekwonV&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Robot(new&amp;nbsp;Punch());&amp;nbsp;//&amp;nbsp;처음엔&amp;nbsp;주먹 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;taekwonV.fight();&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;출력:&amp;nbsp;주먹&amp;nbsp;발사! &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&quot;---&amp;nbsp;무기&amp;nbsp;업그레이드&amp;nbsp;---&quot;); &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;taekwonV.setWeapon(new&amp;nbsp;Missile());&amp;nbsp;//&amp;nbsp;미사일로&amp;nbsp;즉시&amp;nbsp;교체 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;taekwonV.fight();&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;출력:&amp;nbsp;미사일&amp;nbsp;발사! &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HAS-A 관계는 단순히 객체를 필드로 갖는 것을 넘어, 코드를 수정하지 않고도 기능을 확장할 수 있는(OCP 준수) 강력한 무기다. 기능이 필요할 때 &lt;b&gt;상속받을지&lt;/b&gt;를 먼저 생각하지 말고, &lt;b&gt;부품으로 가지자&lt;/b&gt; 라고 생각하는 습관을 들이자.&lt;/p&gt;</description>
      <category>Programming/Skill</category>
      <category>has-a</category>
      <category>OOP</category>
      <category>StrategyPattern</category>
      <category>객체지향</category>
      <category>디자인패턴</category>
      <category>전략패턴</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/242</guid>
      <comments>https://hsunnystory.tistory.com/242#entry242comment</comments>
      <pubDate>Fri, 6 Feb 2026 14:07:35 +0900</pubDate>
    </item>
    <item>
      <title>상속(IS-A)과 조합(HAS-A)</title>
      <link>https://hsunnystory.tistory.com/241</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;객체지향 프로그래밍(OOP)을 처음 배울 때 &lt;b&gt;상속(Inheritance)은 재사용을 위한 도구&lt;/b&gt;라고 배운다. 그리고 &quot;개는 동물이다(Dog is Animal)&quot; 같은 예시를 보며 모든 것을 상속으로 해결하려는 습관을 갖게 된다.&amp;nbsp;하지만 상속을 남용하면, 부모 클래스의 작은 수정이 자식 클래스 전체를 망가뜨리는 &lt;b&gt;깨지기 쉬운 상속(Fragile Base Class)&lt;/b&gt; 문제를 겪게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. IS-A 관계 (Inheritance : 상속)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;~은 ~의 일종이다 (Sub is Super)&lt;/b&gt;가 성립할 때 사용하는 관계다. Java에서는 extends 키워드를 사요하며, 부모와 자식이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;강하게 결합(Tightly Coupled)&lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 예시 : GalaxyS25 is a SmartPhone&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 특징 : 부모의 기능을 물려받아 구현 없이 바로 쓸 수 있다. 하지만 부모가 변경되면 자식도 강제로 영향을 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. IS -A의 단점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현실 세계의 &quot;IS-A&quot;를 코드에 그대로 대입하면 논리적 오류가 발생할 수 있다 .대표적인 예가 &lt;b&gt;&quot;정사각형(Squre)은 직사각형(Rectangle)이다&quot;&lt;/b&gt; 라는 명제다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[Bad Code]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;class&amp;nbsp;Rectangle&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;void&amp;nbsp;setWidth(int&amp;nbsp;w)&amp;nbsp;{&amp;nbsp;this.width&amp;nbsp;=&amp;nbsp;w;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;void&amp;nbsp;setHeight(int&amp;nbsp;h)&amp;nbsp;{&amp;nbsp;this.height&amp;nbsp;=&amp;nbsp;h;&amp;nbsp;} &lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;class&amp;nbsp;Square&amp;nbsp;extends&amp;nbsp;Rectangle&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Override &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;void&amp;nbsp;setWidth(int&amp;nbsp;w)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;정사각형은&amp;nbsp;가로/세로가&amp;nbsp;같아야&amp;nbsp;하므로&amp;nbsp;강제로&amp;nbsp;둘&amp;nbsp;다&amp;nbsp;변경 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;super.setWidth(w); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;super.setHeight(w); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rectangle을 사용하는 코드는 가로만 바꿨는데 세로까지 변할 것이라 예상하지 못한다. 이는 객체지향 원칙 중 &lt;b&gt;리스코프 치환 원칙(LSP)&lt;/b&gt;을 위반하는 사례다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. HAS-A 관계(Composition : 조합)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;~은 ~을 가지고 있다 (Container has a Component)&lt;/b&gt;가 성립할 때 사용하는 관계다. 상속받는 대신, 해당 클래스의 객체를 &lt;b&gt;필드(멤버 변수)&lt;/b&gt;로 소유하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 예시 : Car has an Wheel&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 특징 : 두 클래스가 &lt;b&gt;느슨하게 결합(Loosely Coupled)&lt;/b&gt;된다. 부품을 갈아 끼우듯 유연한 설계가 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. IS-A VS HAS-A&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-sheets-baot=&quot;1&quot; data-sheets-root=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;구분&lt;/td&gt;
&lt;td&gt;IS-A (상속)&lt;/td&gt;
&lt;td&gt;HAS-A (조합)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;개념&lt;/td&gt;
&lt;td&gt;일종이다 (Type of)&lt;/td&gt;
&lt;td&gt;
&lt;div&gt;
&lt;div&gt;가지고 있다 (Part of)&lt;/div&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;구현&lt;/td&gt;
&lt;td&gt;extends&lt;/td&gt;
&lt;td&gt;new (필드 선언)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;결합도&lt;/td&gt;
&lt;td&gt;높음 (High)&lt;/td&gt;
&lt;td&gt;낮음 (Low)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;유연성&lt;/td&gt;
&lt;td&gt;컴파일 시 결정 (변경 불가)&lt;/td&gt;
&lt;td&gt;
&lt;div&gt;
&lt;div&gt;런타임 시 변경 가능&lt;/div&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;내가 쟤의 자식인가?&lt;/b&gt; 가 100% 확실하지 않다면 상속을 쓰지 말자. 단순히 코드를 재사용하고 싶다면, 상속보다는 &lt;b&gt;조합(HAS-A)&lt;/b&gt;을 사용하는 것이 훨씬 안전하다.&lt;/p&gt;</description>
      <category>Programming/Skill</category>
      <category>extends</category>
      <category>has-a</category>
      <category>is-a</category>
      <category>OOP</category>
      <category>객체지향</category>
      <category>면접질문</category>
      <category>상속</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/241</guid>
      <comments>https://hsunnystory.tistory.com/241#entry241comment</comments>
      <pubDate>Thu, 5 Feb 2026 15:49:48 +0900</pubDate>
    </item>
    <item>
      <title>[Algorithm] 스택(Stack)과 큐(Queue)</title>
      <link>https://hsunnystory.tistory.com/240</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 스택(Stack)&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;개념&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;LIFO(Last In First Out, 후입선출)&lt;/b&gt; 구조&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 가장 나중에 들어온 데이터가 가장 먼저 나가는 형태&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 입구가 출구가 하나인 구조&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;주요 연산&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- push : 데이터를 넣는 연산 (top 위치에 삽입)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- pop : 데이터를 꺼내는 연산 (top 위치 데이터 삭제 및 반환)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- peek : 데이터를 꺼내지 않고 확인만 하는 연산&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;활용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- DFS(깊이 우선 탐색)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 백트래킹 (Backtracking)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 재귀 함수의 원리와 동일&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Java 구현&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java에서 스택을 사용할 때 java.util.Stack 클래스는 사용을 지양해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 이유 : Stack은 초기 Java 버전의 Vector를 상속받아 구현되어 있어, 모든 메서드에 동기화(Synchronized) 키워드가 붙어 있다. 이는 멀티 스레드 환경이 아닐 때 불필요한 오버헤드를 발생시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 대안 : Deque 인터페이스의 구현체인 ArrayDeque를 사용하는 것이 성능면에서 유리하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;//&amp;nbsp;비추천&amp;nbsp;(Legacy) &lt;br /&gt;Stack&amp;lt;Integer&amp;gt;&amp;nbsp;stack&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Stack&amp;lt;&amp;gt;(); &lt;br /&gt;&lt;br /&gt;//&amp;nbsp;추천&amp;nbsp;(Modern&amp;nbsp;Java) &lt;br /&gt;Deque&amp;lt;Integer&amp;gt;&amp;nbsp;stack&amp;nbsp;=&amp;nbsp;new&amp;nbsp;ArrayDeque&amp;lt;&amp;gt;(); &lt;br /&gt;&lt;br /&gt;stack.push(1); &lt;br /&gt;stack.push(2); &lt;br /&gt;stack.peek();&amp;nbsp;//&amp;nbsp;2&amp;nbsp;출력 &lt;br /&gt;stack.pop();&amp;nbsp;&amp;nbsp;//&amp;nbsp;2&amp;nbsp;반환&amp;nbsp;및&amp;nbsp;삭제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 큐(Queue)&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;개념&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;FIFO (First In First Out, 선입선출)&lt;/b&gt; 구조&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 먼저 들어온 데이터가 먼저 나가는 형태&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 양쪽이 뚫려있는 구조&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;주요 연산&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- add / offer : 데이터를 넣는 연산 (rear/tail 위치에 삽입). offer는 실패 시 예외 대신 false를 반환하므로 더 안정적&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- remove / poll : 데이터를 꺼내는 연산 (front/head 위치 데이터 삭제 및 변환) poll은 비어 있을 때 null을 반환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- element / peek : 데이터를 확인만 하는 연산&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;활용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- BFS (너비 우선 탐색)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 캐시(Cache) 구현&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 프로세스 스케줄링&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Java 구현(LinkedList vs ArrayDeque)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Queue는 인터페이스이므로 구현체가 필요하다. 보통 LinkedList와 ArrayDeque 중 하나를 선택한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- LinkedList : 큐의 중간에 데이터를 삽입/삭제할 일이 많다면 유리하다. 하지만 각 요소가 노드 객체로 생성되므로 메모리 오버헤드가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- ArrayDeque : 큐의 양 끝에서만 연산이 일어난다면(순수 큐 기능) 메모리 효율과 속도(Cache Locality)면에서 LinkedList 보다 유리하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;//&amp;nbsp;일반적인&amp;nbsp;구현&amp;nbsp;(LinkedList&amp;nbsp;사용) &lt;br /&gt;Queue&amp;lt;Integer&amp;gt;&amp;nbsp;queue&amp;nbsp;=&amp;nbsp;new&amp;nbsp;LinkedList&amp;lt;&amp;gt;(); &lt;br /&gt;&lt;br /&gt;//&amp;nbsp;성능&amp;nbsp;중시&amp;nbsp;(ArrayDeque&amp;nbsp;사용) &lt;br /&gt;Queue&amp;lt;Integer&amp;gt;&amp;nbsp;queue&amp;nbsp;=&amp;nbsp;new&amp;nbsp;ArrayDeque&amp;lt;&amp;gt;(); &lt;br /&gt;&lt;br /&gt;queue.offer(1); &lt;br /&gt;queue.offer(2); &lt;br /&gt;queue.peek();&amp;nbsp;//&amp;nbsp;1&amp;nbsp;출력&amp;nbsp;(먼저&amp;nbsp;들어간&amp;nbsp;값) &lt;br /&gt;queue.poll();&amp;nbsp;//&amp;nbsp;1&amp;nbsp;반환&amp;nbsp;및&amp;nbsp;삭제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 우선순위 큐(Priority Queue)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 들어간 순서와 상관 없이 &lt;b&gt;우선순위가 높은 데이터&lt;/b&gt;가 먼저 나오는 자료구조&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;b&gt;힙(Heap)&lt;/b&gt; 자료구조를 기반으로 구현&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- Java에서는 PriorityQueue 클래스를 사용하며, 기본적으로 낮은 숫자(Min Heap)가 높은 우선순위를 가진다.(높은 숫자를 우선시 하려면 Collections.reverseOrder() 사용)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;예시)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;//&amp;nbsp;낮은&amp;nbsp;숫자가&amp;nbsp;우선&amp;nbsp;(Min&amp;nbsp;Heap) &lt;br /&gt;PriorityQueue&amp;lt;Integer&amp;gt;&amp;nbsp;pq&amp;nbsp;=&amp;nbsp;new&amp;nbsp;PriorityQueue&amp;lt;&amp;gt;(); &lt;br /&gt;&lt;br /&gt;//&amp;nbsp;높은&amp;nbsp;숫자가&amp;nbsp;우선&amp;nbsp;(Max&amp;nbsp;Heap) &lt;br /&gt;PriorityQueue&amp;lt;Integer&amp;gt;&amp;nbsp;pq&amp;nbsp;=&amp;nbsp;new&amp;nbsp;PriorityQueue&amp;lt;&amp;gt;(Collections.reverseOrder());&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스택과 큐는 알고리즘 문제 해결의 도구다. 단순히 메서드만 아는 것보다, 문제의 성격(DFS 또는 BFS)에 따라 적절한 자료구조를 선택하고, 효율적인 구현체(ArrayDeque)를 선택하는 것이 중요하다.&lt;/p&gt;</description>
      <category>Programming/Algorithm</category>
      <category>ArrayDeque</category>
      <category>linkedlist</category>
      <category>Queue</category>
      <category>Stack</category>
      <category>스택</category>
      <category>알고리즘</category>
      <category>자료구조</category>
      <category>코딩테스트</category>
      <category>코테</category>
      <category>큐</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/240</guid>
      <comments>https://hsunnystory.tistory.com/240#entry240comment</comments>
      <pubDate>Wed, 4 Feb 2026 14:23:06 +0900</pubDate>
    </item>
    <item>
      <title>[Algorithm] 구간 합 (Prefix Sum) 핵심 개념 및 활용</title>
      <link>https://hsunnystory.tistory.com/239</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;코딩테스트 문제를 풀다 보면 &lt;b&gt;특정 구간의 합을 구하라&lt;/b&gt;는 요구사항을 자주 마주하게 된다. 단순히 반복문으로 그때그때 합을 구하면 될 것 같지만, 데이터의 개수가 많아지고 질의(Query) 횟수가 늘어나면 시간 초과(Time Limit Exceeded)가 발생하기 십상이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 시간 복잡도를 획기적으로 줄여주는 필수 테크닉이 바로 &lt;b&gt;구간 합(Prefix Sum)&lt;/b&gt;과 &lt;b&gt;합 배열(Prefix Sum Array)&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 구간 합&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열 내의 특정 구간 내에 포함된 모든 원소들의 합을 의미한다. 예를 들어 A[2] 부터 A[5] 까지의 합을 구하고 싶을 때, 매번 for 문을 돌리는 것이 아니라 &lt;b&gt;미리 구해둔 합 배열을 이용&lt;/b&gt;해 O(1)의 시간 복잡도로 답을 구해내는 기법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 합 배열 만들기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;합 배열 S는 원본 배열 A의 0번 인덱스부터 특정 인덱스까지의 누적 합을 저장한 배열이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;공식&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span data-index-in-node=&quot;0&quot; data-math=&quot;S[i] = S[i-1] + A[i]&quot;&gt;&amp;nbsp; &amp;nbsp;S[i] = S[i-1] + A[i]&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 &lt;b&gt;현재 인덱스까지의 합 = 이전 인덱스까지의 합 + 현재 값&lt;/b&gt; 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;예시)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 원본 배열 A : [3, 6, 5, 10, 14]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 합 배열 S : [3, 9, 14, 24, 38]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; S[0] = A[0] = 3&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; S[1] = S[0] + A[1] =&amp;gt; 3 + 6 = 9&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; S[2] = S[1] + A[2] =&amp;gt; 9 + 5 = 14&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; ....&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 구간 합 빠르게 구하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;합 배열 S가 준비되었다면, i번째부터 j번째까지의 구간 합은 뺄셈 연산 한 번으로 구할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;공식&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp;구간 합(i~j) = S[j] - S[i-1]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;원리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- S[j] : 처음부터 J 까지의 합&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- S[i-1] : 처음부터 i 바로 전까지의 합&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;따라서 S[j]에서 S[i-1]을 빼면, i부터 j까지의 합만 남게 된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;예시) A[2] ~ A[4] 구간의 합을 구하고 싶다면?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 공식 적용 : S[4] - S[1]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 계산 : 38 - 9 = 29&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 검증 : 실제 값 5 + 10 + 14 = 29 (일치)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. 사용 이유(시간 복잡도 비교)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터의 개수가 N개이고, 구간 합을 횟수(쿼리)가 M번이라고 가정하자.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 84px;&quot; border=&quot;1&quot; data-sheets-baot=&quot;1&quot; data-sheets-root=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;구분&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;일반적인 방법 (이중 for문)&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;
&lt;div&gt;
&lt;div&gt;구간 합 (합 배열) 사용&lt;/div&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;전처리&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;없음&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;
&lt;div&gt;
&lt;div&gt;O(N) (합 배열 생성)&lt;/div&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;1회 조회&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;O(N)&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;전체 복잡도&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;O(N&amp;times;M)&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;O(N+M)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리가 많을 수록 구간 합 방식이 압도적으로 빠르다. 코딩테스트에서 N과 M이 10만 단위를 넘어간다면 &lt;b&gt;필수적으로 사용&lt;/b&gt;해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 주의사항 및 한계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;b&gt;-&lt;/b&gt; &lt;b&gt;배열 값의 변경&lt;/b&gt; : 만약 원본 배열의 값이 중간에 자주 바뀐다면, 그때마다 합 배열을 다시 만들어야 하므로 비효율적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;-&lt;/b&gt; &lt;b&gt;데이터 변경이 빈번한 경우&lt;/b&gt; : 이때는 &lt;b&gt;세그먼트 트리(Segment Tree)&lt;/b&gt;나 &lt;b&gt;인덱스 트리(Index Tree)&lt;/b&gt; 같은 더 고급 자료구조를 사용해야 한다. (추후 포스팅 예정)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;- 인덱스 주의&lt;/b&gt; : 합 배열 공식에서 S[i-1]을 참조하므로, 인덱스 0번 처리에 주의하거나 배열 크기를 N+1로 잡아 1번 인덱스부터 사용하는 팁이 자주 쓰인다.&lt;/p&gt;</description>
      <category>Programming/Algorithm</category>
      <category>prefixsum</category>
      <category>PS</category>
      <category>구간합</category>
      <category>누적합</category>
      <category>시간복잡도</category>
      <category>알고리즘</category>
      <category>자료구조</category>
      <category>코딩테스트</category>
      <category>코테</category>
      <category>합배열</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/239</guid>
      <comments>https://hsunnystory.tistory.com/239#entry239comment</comments>
      <pubDate>Tue, 3 Feb 2026 11:00:23 +0900</pubDate>
    </item>
    <item>
      <title>[Design Pattern] 객체지향 설계 SOLID 원칙</title>
      <link>https://hsunnystory.tistory.com/238</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 개발자가 되기 위해 반드시 넘어야 할 산이 있다. 바로 객체지향 설계의 5가지 원칙, SOLID다. 로버트 마틴(Robert C. Martin, a.k.a Uncle Bob)이 정립한 이 원칙들은 &lt;b&gt;유지보수 하기 쉽고, 유연하며, 확장이 용이한 소프트웨어&lt;/b&gt;를 만들기 위한 핵심 가이드라인이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;면접 단골 질문이기도 하지만, 실무에서 스파게티 코드를 만들지 않기 위해 반드시 몸에 익혀야 할 5가지 원칙을 정리해 본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. SRP (Single Responsibility Principle) : 단일 책임 원칙&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp;&lt;span style=&quot;color: #000000;&quot;&gt;한 클래스는 하나의 책임만 가져야 한다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 의미 : 클래스를 변경해야 할 이유는 단 하나뿐이어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- Bad : User 클래스가 로그인도 하고, 이메일도 보내고, DB에 저장도 한다.(하나의 수정이 다른 기능에 영향을 줌)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- Good : User 클래스는 정보만 관리하고, AuthManager가 로그인을, EmailSender가 메일을 담당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&amp;nbsp;-&amp;gt; 기능을 분리&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. OCP (Open-Closed Principle) : 개방-폐쇄 원칙&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp;&lt;span style=&quot;color: #000000;&quot;&gt;확장에는 열려 있고, 변경에는 닫혀 있어야 한다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 의미 : 기존의 코드를 변경하지 않으면서, 기능을 추가할 수 있도록 설계해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- Bad : 결제 수단을 추가할 때 마다 If (type == CARD) ... else if (type == CASH) ... 처럼 기존 코드를 계속 수정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- Good : Payment 인터페이스를 만들고 CardPayment, CashPayment가 이를 구현하게 한다. (다형성 활용)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&amp;nbsp;-&amp;gt; 인터페이스를 통해 추상화&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. LSP (Liskov Substitution Principle) : 리스코프 치환 원칙&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp;&lt;span style=&quot;color: #000000;&quot;&gt;자식 클래스는 언제나 부모 클래스를 대체할 수 있어야 한다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 의미 : 부모 클래스의 인스턴스를 자식 클래스의 인스턴스로 바꿔 끼워도 프로그램이 정상적으로 동작해야 한다. 상속을 올바르게 사용했는지 확인하는 원칙이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- Bad : 직사각형을 상속받은 정사각형 클래스. 정사각형은 너비와 높이가 같아야 하므로, 너비만 바꾸는 부모(직사각형)의 메서드를 그대로 쓰면 논리적 오류가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- Good : 둘은 상속 관계가 아닌, 별도의 클래스로 분리하거나 상위 인터페이스 Shape를 둔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&amp;nbsp;-&amp;gt; 상속은 Is-a 관계가 확실할 때만 사용&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. ISP (Interface Segregation Principle) : 인터페이스 분리 원칙&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp; &amp;nbsp;&lt;b&gt;클라이언트는 자신이 사용하지 않는 메서드에 의존하면 안 된다.&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 의미 : 하나의 거대한 인터페이스보다는 구체적인 여러 개의 인터페이스가 낫다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- Bad : SmartPhone 인터페이스에 Call(), sms(), wirelessCharge()가 다 있다. 무선 충전 기능이 없는 구형 폰도 억지로 wirelessCharge()를 구현해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- Good : Phone(통화, 문자)과 WirelessChargable(무선충전) 인터페이스로 분리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&amp;nbsp;-&amp;gt; 인터페이스를 간결화&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. DIP (Dependency Iversion Principle) : 의존 역전 원칙&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp;구체적인 클래스보다 추상화된 것에 의존해야 한다.&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 의미 : 변하기 쉬운 것(구체 클래스)에 의존하지 말고, 변하지 않는 것(인터페이스, 추상 클래스)에 의존&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- Bad : Store 클래스가 SamsungCard 클래스를 직접 new 해서 사용한다.(다른 카드로 변경이 어려움)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- Good : Store 클래스는 CreditCard 인터페이스에 의존하고, 실행 시점에 SamsungCard나 KakaoCard를 주입(DI) 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&amp;nbsp;-&amp;gt; 직접 생성하지 말고 주입 (Dependency Injection)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SOLID 원칙을 지키는 것은 당장은 코드가 많아지고 복잡해 보일 수 있다. 하지만 프로젝트 규모가 커지고 유지보수 기간이 길어질수록, 이 원칙들이 만들어낸 &lt;b&gt;유연함&lt;/b&gt;과 &lt;b&gt;안정성&lt;/b&gt;이 빛을 발하게 된다. 결국 좋은 설계란 &lt;b&gt;미래의 나, 그리고 동료들이 덜 고통받게 하는 배려&lt;/b&gt;다.&lt;/p&gt;</description>
      <category>Programming/Design Pattern</category>
      <category>cleancode</category>
      <category>DIP</category>
      <category>isp</category>
      <category>LSP</category>
      <category>OCP</category>
      <category>OOP</category>
      <category>solid</category>
      <category>SRP</category>
      <category>객체지향</category>
      <category>리팩토링</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/238</guid>
      <comments>https://hsunnystory.tistory.com/238#entry238comment</comments>
      <pubDate>Tue, 3 Feb 2026 10:40:17 +0900</pubDate>
    </item>
    <item>
      <title>[Design Pattern] 객체지향의 원칙 : OCP (개방-패쇄 원칙)</title>
      <link>https://hsunnystory.tistory.com/237</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 코드를 짜기 위한 5가지 원칙(SOLID) 중, 가장 중요하면서도 아이러니한 이름을 가진 원칙이 OCP(Open-Closed principle)다. &lt;b&gt;열려 있으면서 동시에 닫혀 있어야 한다&lt;/b&gt;는 말이 언뜻 보면 모순처럼 들리지만, 이 원칙을 이해하는 순간 객체지향의 진짜 매력을 알게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;OCP(Open_Closed Princilple)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버트란트 마이어(Bertrand Meyer)가 정의한 이 원칙의 핵심은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;소프트웨어 요소(클래스, 모듈, 함수 등)는 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- Software entities should be open for extension, but closed for modification.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;확장에 열려 있다 (Open for Extension)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 새로운 기능이나 요구사항이 생겼을 때, 기존 코드를 확장하여 쉽게 추가할 수 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;변경에 닫혀 있다 (Closed for Modification)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 새로운 기능을 추가할 때, &lt;b&gt;기존의 코드는 수정되지 않아야 한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;OCP를 위반한 사례&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알림(Notification)을 보내는 기능을 만든다고 가정해보자. 처음엔 이메일만 있으면 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public&amp;nbsp;class&amp;nbsp;NotificationSender&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;send(String&amp;nbsp;type)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(type.equals(&quot;EMAIL&quot;))&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&quot;이메일&amp;nbsp;발송&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;나중에&amp;nbsp;SMS&amp;nbsp;기능&amp;nbsp;추가&amp;nbsp;요청이&amp;nbsp;들어옴&amp;nbsp;-&amp;gt;&amp;nbsp;기존&amp;nbsp;코드를&amp;nbsp;수정해야&amp;nbsp;함! &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else&amp;nbsp;if&amp;nbsp;(type.equals(&quot;SMS&quot;))&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&quot;문자&amp;nbsp;발송&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;문제점&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 새로운 알림 수단(카카오톡, 슬랙 등)이 추가될 때 마다 NofificationSender 클래스의 if-else 블록을 계속 &lt;b&gt;수정&lt;/b&gt;해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 기존 코드를 건드리기 때문에, 잘 돌아가던 이메일 기능에 버그가 생길 위험이 있다.(&lt;b&gt;변경에 닫혀 있지 않음&lt;/b&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;OCP를 준수한 사례&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OCP를 지키기 위한 핵심 키워드는 &lt;b&gt;추상화(Abstraction)&lt;/b&gt;와 &lt;b&gt;다형성(Polymorphism)&lt;/b&gt;이다. 변하지 않는 것(공통 역할)을 인터페이스로 격리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 인터페이스 정의(역할)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public&amp;nbsp;interface&amp;nbsp;Notification&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;void&amp;nbsp;send(); &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 구현 클래스 작성(확장)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public&amp;nbsp;class&amp;nbsp;EmailNotification&amp;nbsp;implements&amp;nbsp;Notification&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Override &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;send()&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&quot;이메일&amp;nbsp;발송&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;public&amp;nbsp;class&amp;nbsp;SmsNotification&amp;nbsp;implements&amp;nbsp;Notification&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Override &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;send()&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&quot;문자&amp;nbsp;발송&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 클라이언트 코드&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public&amp;nbsp;class&amp;nbsp;NotificationSender&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;구체적인&amp;nbsp;타입(Email,&amp;nbsp;Sms)을&amp;nbsp;몰라도&amp;nbsp;됨.&amp;nbsp;인터페이스에만&amp;nbsp;의존. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;sendNotification(Notification&amp;nbsp;notification)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;notification.send();&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;결과&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 이제 카카오톡 알림을 추가하고 싶다면? NotificationSender를 수정할 필요 없이, 그냥 KakaoNotification 클래스를 새로 만들면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;- 확장에는 열려 있고(클래스 추가 기능), 변경에는 닫혀 있다(Sender 코드 수정 불필요)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OCP가 중요한 이유&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 유지보수성 : 기존 코드를 건드리지 않으므로, 새 기능을 추가할 때 기존 기능이 망가질까봐 걱정할 필요가 없다. (Regression Bug 방지)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 유연성 : 레고 블록 갈아 끼우듯 기능을 쉽게 교체할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 가독성 : 거대한 if-else 지옥에서 벗어날 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OCP는 코드를 수정하지 말고 코드를 더해라(Add code, don't change code)라는 말로 요약할 수 있다. if문이나 switch 문으로 타입을 구분하고 있다면, OCP를 위반하고 있을 확률이 높다. 이때는 인터페이스를 통한 다형성 적용을 고려해 보자.&lt;/p&gt;</description>
      <category>Programming/Design Pattern</category>
      <category>OCP</category>
      <category>OOP</category>
      <category>solid</category>
      <category>개방패쇄원칙</category>
      <category>객체지향</category>
      <category>다형성</category>
      <category>디자인패턴</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/237</guid>
      <comments>https://hsunnystory.tistory.com/237#entry237comment</comments>
      <pubDate>Mon, 2 Feb 2026 11:39:00 +0900</pubDate>
    </item>
    <item>
      <title>[Algorithm] 배열(Array) vs 리스트(List) 비교 및 선택 기준</title>
      <link>https://hsunnystory.tistory.com/236</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;코팅테스트나 실무 개발에서 가장 기본이 되면서도 중요한 자료구조는 단연 배열(Array)과 리스트(List)다. 너무 익숙해서 대충 넘어가기 쉽지만, 이 둘의 차이를 명확히 알아야 문제 상황에 맞는 자료구로를 선택할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 배열(Array) : 조회에 특화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리의 &lt;b&gt;연속된 공간&lt;/b&gt;에 값이 채워져 있는 형태다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;특징&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 인덱스(Index) 접근 : 인덱스를 알면 즉시 값에 접근할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 크기 고정 : 선언할 때 크기를 지정해야 하며, 한번 선언하면 &lt;b&gt;크기를 늘리거나 줄일 수 없다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 삽입/삭제 비효율 : 중간에 값을 넣거나 빼려면, 그 뒤의 모든 데이터를 한 칸씩 밀거나 당겨야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;시간 복잡도&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 조회(Access) : O(1) (매우빠름)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 삽입/삭제(Insert/Delete) O(N) (데이터를 밀어야 하므로 느림)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 리스트(List) : 삽입/삭제(Modification)에 유연함&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값과 다음 노드를 가리키는 포인터(참조)가 묶여 있는 노드(Node)들의 연결이다. (엄밀히 말하면 LinkedList를 의미)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;특징&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 순차 접근 : 인덱스가 없으므로, 원하는 값에 도달하려면 첫 노드(Head)부터 포인터를 따라가며 찾아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 크기 가변 : 선언 시 크기를 지정할 필요가 없으며, 데이터 개수에 따라 동적으로 늘어나거나 줄어든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 삽입/삭제 효율 : 중간에 값을 넣을 때, 앞뒤 노드의 &lt;b&gt;포인터 연결만 바꿔주면 끝&lt;/b&gt;이다. (데이터 이동 불필요)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;시간복잡도&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 조회(Access) : O(N)( (앞에서부터 찾아가야 하므로 상대적 느림)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 삽입/삭제(Insert/Delete) : O(1) (위치를 알고 있다는 가정하에)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. ArrayList vs LinkedList&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-sheets-baot=&quot;1&quot; data-sheets-root=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;구분&lt;/td&gt;
&lt;td&gt;배열 (Array)&lt;/td&gt;
&lt;td&gt;ArrayList&lt;/td&gt;
&lt;td&gt;LinkedList&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;기반&lt;/td&gt;
&lt;td&gt;Primitive / Object&lt;/td&gt;
&lt;td&gt;Array 기반 (동적 배열)&lt;/td&gt;
&lt;td&gt;
&lt;div&gt;
&lt;div&gt;Node 기반 (연결 리스트)&lt;/div&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;조회 속도&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;td&gt;O(1) (인덱스 지원)&lt;/td&gt;
&lt;td&gt;O(N)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;중간 삽입/삭제&lt;/td&gt;
&lt;td&gt;O(N)&lt;/td&gt;
&lt;td&gt;O(N) (배열 복사 발생)&lt;/td&gt;
&lt;td&gt;
&lt;div&gt;
&lt;div&gt;O(1) (연결만 변경)&lt;/div&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;크기 변경&lt;/td&gt;
&lt;td&gt;불가&lt;/td&gt;
&lt;td&gt;자동 증가 (Resizing)&lt;/td&gt;
&lt;td&gt;자동&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. ArrayList는 리스트인가 배열인가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름은 List지만 내부 구현은 배열(Array)로 되어 있다. 따라서 인덱스 조회는 빠르지만, 중간 삽입/삭제는 배열처럼 느리다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;순수하게 삽입/삭제가 빈번하다면 LinkedList를, 조회 위주라면 ArrayList를 사용&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 정리&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;배열(Array) 또는 ArrayList를 써야할 때&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 데이터의 개수가 정해져 있거나, 변화가 거의 없을 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 데이터의 조회(Access)나 인덱스 활용이 많을 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 대부분의 알고리즘 문제(DFS/BFS, 투 포인터 등)는 접근 속도가 빠른 배열이 유리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;LinkedList를 써야할 때&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 데이터의 크기를 예측할 수 없을 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 중간 위치에 데이터 삽입/삭제가 매우 빈번하게 일어날 때 (단, 조회가 많다면 비추천)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;무조건 ArrayList만 쓰지 말고, 문제의 특성(조회가 많은지, 삽입 삭제가 많은지)를 파악하여 적절한 자료구조를 선택&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>Programming/Algorithm</category>
      <category>array</category>
      <category>ArrayList</category>
      <category>linkedlist</category>
      <category>리스트</category>
      <category>배열</category>
      <category>자료구조</category>
      <category>코딩테스트</category>
      <category>코테</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/236</guid>
      <comments>https://hsunnystory.tistory.com/236#entry236comment</comments>
      <pubDate>Fri, 30 Jan 2026 11:14:26 +0900</pubDate>
    </item>
    <item>
      <title>[Design Pattern] 다형성(Polymorphism)으로 if-else 없애기</title>
      <link>https://hsunnystory.tistory.com/235</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 짜다 보면 기능은 정상적으로 동작하지만, 뭔가 지저분하고 유지보수하기 힘든 코드를 마주하게 된다. 이를 코드 스멜(Code Smell)이라고 한다. 대표적인 코드 스멜 중 하나가 객체의 타입에 따라 if-else나 switch 문을 길게 늘어놓는 경우다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제상황&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쇼핑몰 결제 시스템을 개발한다고 가정해보자. 카드 결제, 무통장 입금, 카카오페이 등 다양한 결제 수단이 있다. 다형성을 활용하지 않고 구현하면 보통 아래와 같은 형태가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public&amp;nbsp;class&amp;nbsp;PaymentProcessor&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;process(Object&amp;nbsp;payment)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;[Code&amp;nbsp;Smell]&amp;nbsp;새로운&amp;nbsp;결제&amp;nbsp;수단이&amp;nbsp;추가될&amp;nbsp;때마다&amp;nbsp;if문이&amp;nbsp;계속&amp;nbsp;늘어남 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(payment&amp;nbsp;instanceof&amp;nbsp;CreditCard)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;((CreditCard)&amp;nbsp;payment).processCard(); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;else&amp;nbsp;if&amp;nbsp;(payment&amp;nbsp;instanceof&amp;nbsp;BankTransfer)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;((BankTransfer)&amp;nbsp;payment).transfer(); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;else&amp;nbsp;if&amp;nbsp;(payment&amp;nbsp;instanceof&amp;nbsp;KakaoPay)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;((KakaoPay)&amp;nbsp;payment).payWithApp(); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;else&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw&amp;nbsp;new&amp;nbsp;IllegalArgumentException(&quot;지원하지&amp;nbsp;않는&amp;nbsp;결제&amp;nbsp;방식&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;이 코드의 문제점&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- OCP(Open-Closed Principle) 위반 : 새로운 결제 수단을 추가하려면 기존의 PaymentProcessor 코드를 수정해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 가독성 저하 : if-else 블록이 길어질수록 로직을 파악하기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 실수 유발 : 타입 캐스팅( (CreditCard) payment ) 과정에서 실수할 여지가 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무슨 타입인지(instanceof) 체크하지 않고 부모 인터페이스를 통해 제어하면 된다.(&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;다형성 적용&lt;/b&gt;&lt;/span&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 공통 인터페이스 정의&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 결제수단이 공통적으로 가져야 할 행동을 정의한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public&amp;nbsp;interface&amp;nbsp;Payment&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;void&amp;nbsp;pay();&amp;nbsp;//&amp;nbsp;구체적인&amp;nbsp;구현은&amp;nbsp;자식&amp;nbsp;클래스에게&amp;nbsp;위임 &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 구현클래스 작성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 결제 수단별로 상세 로직을 구현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public&amp;nbsp;class&amp;nbsp;CreditCard&amp;nbsp;implements&amp;nbsp;Payment&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Override &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;pay()&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&quot;신용카드로&amp;nbsp;결제합니다.&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;public&amp;nbsp;class&amp;nbsp;KakaoPay&amp;nbsp;implements&amp;nbsp;Payment&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Override &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;pay()&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&quot;카카오페이&amp;nbsp;앱으로&amp;nbsp;결제합니다.&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 클라이언트 코드 (Refactoring 완료)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 PaymentProcessor는 구체적인 결제 수단을 알 필요가 없다. 단지 Payment 인터페이스에 의존할 뿐이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public&amp;nbsp;class&amp;nbsp;PaymentProcessor&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;코드가&amp;nbsp;매우&amp;nbsp;간결해짐 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;process(Payment&amp;nbsp;payment)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;payment.pay();&amp;nbsp;//&amp;nbsp;다형성에&amp;nbsp;의해&amp;nbsp;실제&amp;nbsp;객체의&amp;nbsp;메서드가&amp;nbsp;실행됨 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리 : 왜 다형성을 써야 하는가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 유지보수성 향상 : 새로운 결제 수단이 추가되어도 PaymentProcessor 코드는 단 한 줄도 수정할 필요가 없다.(OCP 준수)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 캡슐화 : 각 결제 로직이 해당 클래스(CreditCard, KakaoPay) 내부에 안전하게 격리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 안전성 : 지저분한 형변환(Casting)과 instanceof 검사가 사라져 런타임 오류 가능성이 줄어딘다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 지향의 핵심은 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;조건문을 객체 간의 관계로 대체하는것&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Programming/Design Pattern</category>
      <category>cleancode</category>
      <category>CodeSmell</category>
      <category>OOP</category>
      <category>polymorphism</category>
      <category>Refactoring</category>
      <category>객체지향</category>
      <category>다형성</category>
      <category>디자인패턴</category>
      <category>리팩토링</category>
      <category>코드스멜</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/235</guid>
      <comments>https://hsunnystory.tistory.com/235#entry235comment</comments>
      <pubDate>Thu, 29 Jan 2026 14:10:23 +0900</pubDate>
    </item>
    <item>
      <title>코드 스멜(Code Smell) 이란?</title>
      <link>https://hsunnystory.tistory.com/234</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;개발을 하다 보면 기능은 문제 없이 돌아가는데, 어딘가 모르게 코드를 수정하기 꺼려지고 읽기 힘든 경우가 있다. 마치 냉장고 구석에서 나는 불쾌한 냄새처럼, 코드에서 &lt;b&gt;나중에 문제 생길 것 같은 징조&lt;/b&gt;가 느껴지는 상황. 이것을 &lt;b&gt;코드 스멜(Code Smell)&lt;/b&gt; 이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코드 스멜(Code Smell)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 정의 : 코드가 기술적으로 틀린 것은 아니지만, &lt;b&gt;설계상의 문제나 오류를 유발할 가능성이 높은 코드 패턴&lt;/b&gt;을 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 유래 : Kent Beck와 Martin Fowler의 저서 Refactoring에서 처음 등장한 용어다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 의미 : &quot;이 코드는 당장은 돌아가지만, 나중에 유지보수하기 지옥이야&quot; 라는 경고 신호다. 즉, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;리펙토링(Refactoring)이 필요하다는 신호&lt;/b&gt;&lt;/span&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;대표적인 코드 스멜 유형&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 중복 코드(Duplicated Code)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 증상 : 똑같은 코드 구조가 두 군데 이상 존재&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 문제 : 요구사항이 바뀌면 중복된 모든 곳을 찾아서 수정해야 한다. (하나라도 빼먹으면 에러 발생)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 해결 : &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;메서드 추출(Extract Method)&lt;/b&gt;&lt;/span&gt;을 통해 공통 로직을 하나로 모은다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 긴 메서드(Long Method)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 증상 : 하나의 함수가 너무 길다. (스크롤을 한참 내려야 끝이 보인다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 문제 : 한 메서드가 너무 많은 일을 하고 있어(SRP 위반), 읽기도 힘들고 재사용도 불가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 해결 : 기능 단위로 메서드를 잘게 쪼갠다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 거대한 클래스(Large Class / God Class)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 증상 : 클래스 하나에 필드와 메서드가 너무 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 문제 : 모든 기능을 혼자 다 하려다 보니 결합도가 높아져 수정이 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 해결 : &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;클래스 추출(Extract Class)&lt;/b&gt;&lt;/span&gt;을 통해 책임을 분리한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. 매직 넘버(Magic Number)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 증상 : 코드 중간에 3.14, 86400 같은 숫자가 뜬금없이 박혀 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 문제 : 이 숫자가 무엇을 의미하는지 작성자만 안다. 나중에 이 값을 바꿔야 할 때 어디에 쓰였는지 찾기 힘들다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 해결 : &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;static final 상수(Constant)로 선언&lt;/b&gt;&lt;/span&gt;하여 의미 있는 이름을 부여한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5. Switch 문(Switch Statements)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 증상 : 객체의 타입 코드에 따라 switch나 if-else가 길게 나열된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 문제 : 새로운 타입이 추가될 때 마다 분기문을 수정해야 한다. (OCP 위반)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 해결 : &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;다형성(Polymorphism)&lt;/b&gt;&lt;/span&gt;을 이용해 오버라이등으로 해결한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;리팩토링 예시&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;[Bad Code : 냄새나는 코드]&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;//&amp;nbsp;86400이&amp;nbsp;뭔데?&amp;nbsp;하루가&amp;nbsp;몇&amp;nbsp;초인지&amp;nbsp;모르면&amp;nbsp;이해&amp;nbsp;못&amp;nbsp;함 &lt;br /&gt;if&amp;nbsp;(time&amp;nbsp;&amp;gt;&amp;nbsp;86400)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw&amp;nbsp;new&amp;nbsp;IllegalArgumentException(&quot;시간&amp;nbsp;초과&quot;); &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;[Good Code : 향기나는 코드]&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;//&amp;nbsp;상수로&amp;nbsp;의미를&amp;nbsp;명확히&amp;nbsp;부여 &lt;br /&gt;public&amp;nbsp;static&amp;nbsp;final&amp;nbsp;int&amp;nbsp;SECONDS_PER_DAY&amp;nbsp;=&amp;nbsp;86400; &lt;br /&gt;&lt;br /&gt;if&amp;nbsp;(time&amp;nbsp;&amp;gt;&amp;nbsp;SECONDS_PER_DAY)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw&amp;nbsp;new&amp;nbsp;IllegalArgumentException(&quot;시간&amp;nbsp;초과&quot;); &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 스멜을 없애는 과정이 바로 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;리팩토링&lt;/b&gt;&lt;/span&gt;이다. 처음부터 완벽한 코드를 짤 수는 없다. 하지만 코드를 작성하고 나서 찝찝한 냄새가 난다면 그때그때 환기하는 습관을 들이자. 그것이 &lt;b&gt;기술 부채(Technical Debt)&lt;/b&gt;를 줄이는 지름길이다.&lt;/p&gt;</description>
      <category>Programming/Skill</category>
      <category>cleancode</category>
      <category>CodeSmell</category>
      <category>Refactoring</category>
      <category>리팩토링</category>
      <category>코드스멜</category>
      <category>클린코드</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/234</guid>
      <comments>https://hsunnystory.tistory.com/234#entry234comment</comments>
      <pubDate>Tue, 27 Jan 2026 13:56:21 +0900</pubDate>
    </item>
    <item>
      <title>[Java] instanceof 연산자 개념 및 사용법 (feat. Pattern Matching)</title>
      <link>https://hsunnystory.tistory.com/233</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서 객체 지향 프로그래밍을 하다 보면 부모 타입의 참조 변수로 자식 객체를 다루는 업캐스팅(Upcasting) 상황이 자주 발생한다. 이때 다시 원래의 자식 타입으로 되돌리는 다운캐스팅(Downcasting)을 하기 전에 &lt;b&gt;해당 객체가 실제로 어떤 타입인지 확인&lt;/b&gt;해야 안전하다. 이 때 사용하는 것이 instanceof 연산자다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;instanceof 란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체가 특정 클래스(또는 인터페이스)의 인스턴스인지 확인하는 연산자다. 결과는 boolean 타입으로 반환된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;사용법&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 instanceof 클래스명(또는 인터페이스명)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;결과&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 객체가 해당 클래스 타입이거나, 상속받은 자식 클래스라면 &lt;b&gt;true&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 객체가 null이거나, 관련 없는 타입이라면 &lt;b&gt;false&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용 이유(ClassCastException 방지)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무턱대고 형변환(Casting)을 시도하면 실행 시점에 ClassCaseException 에러가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;class&amp;nbsp;Parent&amp;nbsp;{} &lt;br /&gt;class&amp;nbsp;Child&amp;nbsp;extends&amp;nbsp;Parent&amp;nbsp;{} &lt;br /&gt;&lt;br /&gt;public&amp;nbsp;class&amp;nbsp;Main&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;static&amp;nbsp;void&amp;nbsp;main(String[]&amp;nbsp;args)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Parent&amp;nbsp;p&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Parent(); &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 확인 없이 형변환 시도 -&amp;gt; 런타임 에러 발생 (ClassCastException) &lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Child c = (Child) p;&amp;nbsp; &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// instanceof로 타입 체크 후 형변환 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(p&amp;nbsp;instanceof&amp;nbsp;Child)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Child&amp;nbsp;c&amp;nbsp;=&amp;nbsp;(Child)&amp;nbsp;p; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&quot;형변환&amp;nbsp;성공&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;else&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&quot;형변환&amp;nbsp;불가&quot;); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;상속 관계에서의 동작&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;instanceof는 본인 클래스뿐만 아니라 &lt;b&gt;부모 클래스나 구현한 인터페이스에 대해서도 true를 반환 한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Parent&amp;nbsp;p&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Child(); &lt;br /&gt;&lt;br /&gt;System.out.println(p&amp;nbsp;instanceof&amp;nbsp;Object);&amp;nbsp;//&amp;nbsp;true&amp;nbsp;(모든&amp;nbsp;객체의&amp;nbsp;조상) &lt;br /&gt;System.out.println(p&amp;nbsp;instanceof&amp;nbsp;Parent);&amp;nbsp;//&amp;nbsp;true&amp;nbsp;(부모&amp;nbsp;타입) &lt;br /&gt;System.out.println(p&amp;nbsp;instanceof&amp;nbsp;Child);&amp;nbsp;&amp;nbsp;//&amp;nbsp;true&amp;nbsp;(실제&amp;nbsp;인스턴스&amp;nbsp;타입)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Java 14+ : Pattern Matching for instanceof&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 14부터 도입되고 자바 16에서 정식 확정된 기능으로, instanceof 검사와 형변환을 동시에 할 수 있게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기존 방식(Java 14 이전)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;if&amp;nbsp;(obj&amp;nbsp;instanceof&amp;nbsp;String)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;s&amp;nbsp;=&amp;nbsp;(String)&amp;nbsp;obj;&amp;nbsp;//&amp;nbsp;명시적&amp;nbsp;형변환&amp;nbsp;필요 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(s.length()); &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;개선된 방식(Java 16 이후)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;//&amp;nbsp;검사&amp;nbsp;성공&amp;nbsp;시,&amp;nbsp;변수&amp;nbsp;s에&amp;nbsp;자동으로&amp;nbsp;형변환되어&amp;nbsp;바인딩됨 &lt;br /&gt;if&amp;nbsp;(obj&amp;nbsp;instanceof&amp;nbsp;String&amp;nbsp;s)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(s.length());&amp;nbsp;//&amp;nbsp;바로&amp;nbsp;사용&amp;nbsp;가능 &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주의사항&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;instanceof를 남발하는 것은 객체 지향 설계 관점에서 좋지 않다.(Code Smell) 분기문이 길어진다면, 다형성(Polymorphism)을 이용해 메서드 오버라이딩으로 해결하는 것이 올바른 설계 방향이다.&lt;/p&gt;</description>
      <category>Programming/Java</category>
      <category>CalssCastException</category>
      <category>casting</category>
      <category>instanceof</category>
      <category>patternmatching</category>
      <category>형변환</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/233</guid>
      <comments>https://hsunnystory.tistory.com/233#entry233comment</comments>
      <pubDate>Mon, 26 Jan 2026 11:59:44 +0900</pubDate>
    </item>
    <item>
      <title>[Algorithm] 코딩테스트 디버깅(Debugging) 핵심 요령 및 실수 유형</title>
      <link>https://hsunnystory.tistory.com/232</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;코딩테스트에서 문제를 풀다 보면 논리적 오류나 사소한 실수로 인해 정답을 맞히지 못하는 경우가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 대부분은 System.out.println() 같은 로그를 찍어 확인하려 하지만, 실전에서는 디버깅(Debugging) 기능을 사용하는 것이 시간 단축과 정확도 측면에서 훨씬 유리하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 디버깅(Debugging)이 필요한 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 사람들이 코드를 수정할 때 로그를 찍어서 확인한다. 하지만 로그 방식은 다음과 같은 단점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 시간 소요 : 로그를 찍고, 실행하고, 다시 지우고, 재실행하는 과정이 반복되어 시간이 오래 걸린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 시야 협소 : 로그를 찍은 특정 부분만 보게 되어, 코드의 전체적인 흐름을 놓치고 지엽적인 부분에만 매몰되기 쉽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;디버깅 기능을 사용하면 변수의 상태 변화를 실시간으로 추적할 수 있어 논리 오류를 빠르게 찾아낼 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. IDE 디버깅 기능 활용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Eclipse, Intellij 등 대부분의 IDE는 강력한 디버깅 기능을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 중단점(Breakpoint) : 코드의 특정 라인에서 실행을 멈춘다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 변수(Variables) : 현재 시점의 변수 값을 실시간으로 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 수식(Expression) : 단순 변수 값 뿐만 아니라 arr[i] + arr[i] 와 같이 특정 수식의 결과를 즉시 확인해 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 코딩테스트에서 자주하는 실수 유형&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;변수 초기화 오류&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;현상&lt;/b&gt; : 여러 테스트 케이스를 처리하는 문제에서 이전 케이스의 계산 값이 다음 케이스에 영향을 미쳐 오답 발생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결&lt;/b&gt; : for문 내에서 반복될 때 누적 변수(sum 등)나 배열이 적절한 시점에 초기화(0 또는 null) 되는 지 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;반복문 인덱스 범위 오류&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;현상&lt;/b&gt; : N번 반복해야 하는데 N+1번 반복하거나, 배열의 범위를 벗어나는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;체크&lt;/b&gt; : 반복문 조건이 i &amp;lt; N 인지, i &amp;lt;= N 인지 의도한 로직과 정확히 일치하는지 디버깅을 통해 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;출력 형식 오류&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;현상&lt;/b&gt; : 문제에서는 숫자만 출력하라고 했는데, 디버깅이나 확인을 위해 넣은 텍스트를 그대로 포함하여 제출하는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주의&lt;/b&gt; : 불필요한 테스트 출력문이 포함되지 않았는지 마지막까지 확인해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&amp;nbsp;자료형 범위 오류(Overflow)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;현상&lt;/b&gt; : 로직은 완벽한데 답이 틀리거나, 뜬금없이 &lt;b&gt;음수&lt;/b&gt;가 출력되는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;원인&lt;/b&gt; : int 자료형의 범위(약 21억)를 초과하여 오버플로우가 발생했기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결&lt;/b&gt; :&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 답이 음수가 나올 수 없는 로직인데 음수가 나온다면 100% 오버플로우다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 팩토리얼, 순열, 경우의 수 등 값이 급격히 커지는 문제를 처음부터 long 자료형을 사용하는 습관을 들이는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4 정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코딩테스트는 시간 싸움이다. 한 번에 완벽하게 코드를 짜면 좋겠지만, 실수가 발생했을 때 얼마나 빨리 수정하느냐가 합격을 좌우한다. 로그를 찍으며 해매기보다는, 디버깅 툴을 익숙하게 다루는 연습을 통해 디버깅 시간을 단축해야 한다.&lt;/p&gt;</description>
      <category>Programming/Algorithm</category>
      <category>debugging</category>
      <category>Java</category>
      <category>PS</category>
      <category>디버깅</category>
      <category>문제해결</category>
      <category>알고리즘</category>
      <category>오버플로우</category>
      <category>코딩테스트</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/232</guid>
      <comments>https://hsunnystory.tistory.com/232#entry232comment</comments>
      <pubDate>Fri, 23 Jan 2026 17:47:59 +0900</pubDate>
    </item>
    <item>
      <title>[Algorithm] 시간 복잡도 (Time Complexity) 개념 및 Big-O 표기법</title>
      <link>https://hsunnystory.tistory.com/231</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;알고리즘 문제를 풀거나 효율적인 로직을 짤 때 가장 중요한 개념중 하나인 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;시간 복잡도&lt;/b&gt;&lt;/span&gt;에 대해 정리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시간 복잡도&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간복잡도(Time Complexity)는 주어진 문제를 해결하기 위해 필요한 연산 횟수를 의미한다. 실제 걸리는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간(초)를 측정하는 것이 아니라, 입력 데이터의 크기에 따라 연산량이 어떻게 증가하는지를 나타내는 지표다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 Problem Solving 환경에서는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;1억번의 연산 = 1초&lt;/b&gt;&lt;/span&gt; 로 예측한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;시간 복잡도&lt;span&gt; &lt;/span&gt;&lt;/span&gt;표기법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 빅-오메가 (Big-&amp;Omega;) : 최선(Best Case)의 경우&lt;br /&gt;&amp;nbsp;- 빅-세타 (Big-&amp;theta;) : 보통(Average Case)의 경우&lt;br /&gt;&amp;nbsp;- 빅-오 (Big-O) : 최악(Worst Case)의 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알고리즘 성능 평가시에는 데이터가 최악으로 주어졌을때도 처리가 가능한지를 판단해야 하므로 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;빅-오 (Big-O) 표기법이 가장 중요&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시간 복잡도 도출 기준&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 상수는 시간 복잡도에서 제외&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연산횟수가 N이든 3N이든, N이 무한대로 커지면 상수의 영향력은 미미하므로 똑같이 O(N)으로 취급한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 가장 많이 중첩된 반복문의 횟수가 기준&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반복문이 2중으로 중첩되어 있다면 O(N^2), 3중이라면 O(N^3)이 된다. 따라서 가장 깊은 반복문을 찾으면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;데이터 크기와 연산 횟수 확인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간 복잡도를 따질 때는 가장 먼저 데이터의 개수(Input Size)를 확인해야 한다. 전체 연산 횟수는 다음과 같이 계산한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&amp;nbsp; &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&amp;nbsp;연산 횟수 = 알고리즘 시간 복잡도 x 데이터의 크기&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;알고리즘 별 시간 복잡도 예시&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 버블 정렬(Bubble Sort) : &lt;span data-index-in-node=&quot;22&quot; data-math=&quot;O(N^2)&quot;&gt;O(N^2)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 많아질수록 기하급수적으로 속도 저하&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 병합 정렬(Merge Sort) : &lt;span data-index-in-node=&quot;21&quot; data-math=&quot;O(N \log N)&quot;&gt;O(N log N)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;N^2 대비 훨씬 효율적&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;효율적인 알고리즘 적용&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1. 문제의 제한 시간 내에 통과할 수 있는 알맞는 알고리즘을 선택하기 위함&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2. 코드 내에서 비효율적인 로직을 찾아 계산하기 위함&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무조건 복잡한 알고리즘을 쓰는 것이 아니라, 데이터의 크기에 맞춰 적절한 시간 복잡도를 가진 알고리즘을 선택하는 것이 핵심&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Programming/Algorithm</category>
      <category>Algorithm</category>
      <category>coding test</category>
      <category>코딩테스트</category>
      <category>코테</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/231</guid>
      <comments>https://hsunnystory.tistory.com/231#entry231comment</comments>
      <pubDate>Thu, 22 Jan 2026 10:49:35 +0900</pubDate>
    </item>
    <item>
      <title>[TSA] 전자서명에서 타임스탬프가 필요한 이유</title>
      <link>https://hsunnystory.tistory.com/230</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;전자서명은 근본적으로 부인방지 기능으로 개인키의 소유자가 서명했다는 것을 증명한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 서명 시각에 대한 증명은 신뢰성을 갖지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 서명 시각을 넣을 수는 있지만, 클라이언트의 시간을 조작하는 방법 등으로 서명 시간 조작이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 아래와 같은 경우에&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1. 전자 서명 생성&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2. 인증서 폐지 발생&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;전자서명이 인증서 폐지보다 먼저 발생하였느냐의 검증에 문제가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TSA 의 역할&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 이 서명 값이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 해당 시작에 존재&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;-&amp;gt; 전자 서명이 언제 생성되었는지 제 3자가 증명해준다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 타임스탬프가 있다고 무조건 신뢰할 수 있는 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 신뢰 불가한 TSA&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- TSA 인증서/키 문제 발생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 검증 정책(LTV 정책)이 없는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;-&amp;gt; 타임 스탬프는 조건을 만들어 주는 도구일뿐 만능 해결책은 아니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전자서명에서 TSA는 시간 기록 용도가 아니라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;전자서명 인증서의 만료/폐지 이후에도 과거 서명을 검증하기 위한 기반을 만들어준다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>TSA</category>
      <category>Timestamp</category>
      <category>tsa</category>
      <category>시점확인</category>
      <category>전자서명</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/230</guid>
      <comments>https://hsunnystory.tistory.com/230#entry230comment</comments>
      <pubDate>Wed, 21 Jan 2026 14:25:32 +0900</pubDate>
    </item>
    <item>
      <title>[TSA] TimeStampToken(TST) 검증</title>
      <link>https://hsunnystory.tistory.com/229</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;타임스탬프 토큰을 발급 받았다고 끝이 아니다. 운영에서는 검증 역시 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- TSQ &amp;lt;-&amp;gt; TSR 매칭 여부(요청한 응답이 맞는지)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 토큰에 대한 서명/인증서 검증(TSA를 신뢰할 수 있는지)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. TSR status&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TSR(TimeStampResp)에는 status(PKIStatusInfo)가 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- granted / grantedWithMods 등 성공 여부 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. timeStampToken&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;status가 성공이면 timeStampToken이 존재&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. messageImprint&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 원문을 다시 해시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- TSTInfo의 messageImprint(알고리즘+해시값)와 비교&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. nonce&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TSQ에 nonce를 포함하였다면, TSR에도 같은 nonce가 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- TSQ nonce == TSR nonce&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 리플레이 공격 방지용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. policy&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TSTInfo 에는 policy OID가 포함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 허용 정책이 정해져 있다면 필터링&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- TSQ에서 reqPolicy를 요청했다면 응답 policy가 동일한지 확인&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. TimeStampToken 서명 검증&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 토큰(CMS SignedData) 서명 검증&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- signerInfo가 가리키는 TSA 인증서로 검증&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. TSA 인증서 체인 검증&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰에 인증서가 포함되어 있어도 신뢰 여부는 별도로 판단해야 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 루트/중간CA 체인이 신뢰 저장소에 연결되는지 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TimeStampToken 검증은 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;단순한 시간 검증이 아니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- status 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- token 존재 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- messageImprint 일치 여부&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- nonce 매칭&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- policy 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 서명 검증&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>TSA</category>
      <category>RFC3161</category>
      <category>Timestamp</category>
      <category>TST 검증</category>
      <category>시점확인</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/229</guid>
      <comments>https://hsunnystory.tistory.com/229#entry229comment</comments>
      <pubDate>Tue, 20 Jan 2026 11:39:20 +0900</pubDate>
    </item>
    <item>
      <title>Cryptographic Message Syntax(CMS) 구조</title>
      <link>https://hsunnystory.tistory.com/228</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Cryptographic Message Syntax(CMS)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터에 암호 보호를 적용하는 방식을 표준으로 정의한 포멧&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- CMS는 암호 알고리즘이 아님&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 암호 처리 결과를 담는 컨터이너&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RFC5652는 CMS가 다음과 같은 작업을 위해 쓰인다고 설명한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 서명 (Sign)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 해시/요약 (Digest)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 인증/무결성 (Auth)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 암호화(Encrypt)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, CMS는 서명파일, 암호화 파일 같은 것을 만들 때 자주 보게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. ContentInfo&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안에 들어가는 내용 타입을 contentType로 구분하고 그 타입에 맞는 실제 데이터를 content에 담는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; SignedData, EnvelopData 같은 데이터를 한 포맷으로 다룰 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. SignedData&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;원문(또는 원문 해시)&lt;/b&gt;&lt;/span&gt;에 대해 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;누가 어떤 방식으로 서명&lt;/b&gt;&lt;/span&gt;했는지 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;검증에 필요한 인증서 등을 포함&lt;/b&gt;&lt;/span&gt;하여&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;한 덩어리로 묶어둔 구조&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- encapContentInfo : 서명 대상 컨텐츠 (optional)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- signerInfos : 서명자 정보&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- certificates : 인증서 (optional)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- crls : 검증에 필요한 CRL 정보 (optional)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Attached vs Detached&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SignedData는 &lt;b&gt;원문을 포함할 수도&lt;/b&gt; 있고, &lt;b&gt;서명만 포함할 수도&lt;/b&gt; 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- Attached : SignedData 안에 원문 포함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- Detached : SignedData 안에 서명만 있고 원문은 밖에 따로 존재&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증서가 포함 되어 있을 시&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SignedData에 certificates가 들어 있으면 초기 검증이 편해지는 경우가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;개인키로 서명한 것은 공개키로 검증하여아 하는데, SignedData에 인증서를 포함하면 &lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;그 인증서에서 공개키를 추출하여 개인키의 서명을 검증할 수 있기 때문이다. 만약 SignedData에 인증서가 포함되어 있지 않으면, SignedData에 해당하는 공개키를 검증 시스템에서 찾을 수 있는 별도의 방법이 구현되어야 한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그러나 인증서가 들어 있다고 해서 신뢰 된다는 의미가 아니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;신뢰 체인(루트/중간CA)과 정책은 검증 시스템이 결정한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;PKCS#7과의 관계&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;CMS는 PKCS#7 계열에서 발전한 표준이라고 보면 된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- PKCS#7 문법은 RFC2315로 정의&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- CMS는 IETF RFC5652에서 정의&lt;/p&gt;</description>
      <category>IT</category>
      <category>CMS</category>
      <category>PKCS#7</category>
      <category>SignedData</category>
      <category>전자서명</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/228</guid>
      <comments>https://hsunnystory.tistory.com/228#entry228comment</comments>
      <pubDate>Mon, 19 Jan 2026 09:43:32 +0900</pubDate>
    </item>
    <item>
      <title>[TSA] TimeStampToken(TST)</title>
      <link>https://hsunnystory.tistory.com/227</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;TimeStampToken(TST)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TSR(TimeStampResp)에서 제일 중요한 것은 &lt;b&gt;TimeStampToken&lt;/b&gt; 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 값은 &quot;시간 문자열&quot;이 아닌 &lt;b&gt;TSA가 서명해서 주는 토큰&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TimeStampToken은 CMS 구조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RFC3161에서 TimeStampToken은 &lt;b&gt;CMS(SignedData) 컨테이너 구조&lt;/b&gt;를 만든다고 설명한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 토큰 안에는 보통 다음과 같은 것들을 포함한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 서명(Signature)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 서명자(SignerInfo)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- (옵션) 인증서(Cerificates)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 본문 데이터(TSTInfo)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TSTInfo&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TSTInfo는 TimeStampToken의 &lt;b&gt;본문&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RFC3161 기준으로 &lt;b&gt;TSTInfo가 DER 인코딩되어 SignedData의 eContent로 들어간다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 토큰에는 &lt;b&gt;TSA 서명 외에 다른 서명이 들어가면 안된다&lt;/b&gt;고 명시한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TSTInfo 주요필드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. policy&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 어떤 정책(OID)로 발급했는지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 요청에 reqPolicy가 있었다면, 응답 policy도 같아야 한다.(다를 시 unacceptedPolicy)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. messageImprint (중요)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &lt;span style=&quot;color: #ee2323;&quot;&gt;hashAlgorithm OID + hashedMessage(해시 값)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 요청에서 보낸 messageImprint와 &lt;b&gt;동일해야&lt;/b&gt; 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. SerialNumber&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- TSA가 각 토큰에 부여하는 고유 번호&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 같은 TSA 기준으로 유일&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. genTime&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- TSA가 토큰을 생성한 시간&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- UTC 기반으로 표현&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. accuracy (optional)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- genTime의 정확도(오차 범위)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 시간 범위(상한/하한) 계산 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. ordering (default false)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- ordering이 true면, 같은 TSA에서 나온 토큰들을 genTime 기준으로 정렬 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. nonce (optional, but 중요)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 요청에 nonce가 있었으면 응답에도 반드시 존재해야 하고 같아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8. tsa / extensions (optional)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- TSA 식별 정보(GeneralName)나 확장 필드가 들어올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;certReq를 true로 주면 뭐가 달라지나?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청에서 certReq를 true로 주면, 응답 SignedData의 certficates 영역에 &lt;b&gt;TSA 인증서가 포함되어야 한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;(포함됐다. != 신뢰된다. 신뢰 체인은 별도 정책/검증 필요)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TimeStampToken(TST)은 &quot;시간 값&quot;이 아니라 &lt;b&gt;서명된 토큰&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- TST는 CMS(SignedData) 구조&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 본문은 TSTInfo&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 검증에서 제일 중요한 건 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;messageImprint / nonce&lt;/b&gt;&lt;/span&gt; 일치 여부&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;2452&quot; data-start=&quot;2446&quot; data-ke-size=&quot;size23&quot;&gt;참고&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- RFC 3161 (TSTInfo 구조/필드/nonce 규칙 등)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- RFC 5652 (CMS 개요)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- RFC 5816 (RFC3161 업데이트: ESSCertIDv2, SHA-1 외 해시 허용)&lt;/p&gt;</description>
      <category>TSA</category>
      <category>RFC3161</category>
      <category>Timestamp</category>
      <category>tsa</category>
      <category>TST</category>
      <category>TSTInfo</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/227</guid>
      <comments>https://hsunnystory.tistory.com/227#entry227comment</comments>
      <pubDate>Thu, 15 Jan 2026 14:54:38 +0900</pubDate>
    </item>
    <item>
      <title>[TSA] RFC 3161 TimeStamp 프로토콜 구조</title>
      <link>https://hsunnystory.tistory.com/226</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;RFC 3161&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TSA에 보내는 요청 형식과 응답 형식을 정의한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TimeStampReq(TSQ)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트 -&amp;gt; TSA 타임스탬프 발급 요청&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. messageImprint&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- hashAlgorithm OID + hashedMessage(해시값)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- TSA는 원문을 받지 않고 이 해시 값만 다룬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. nonce(optional)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 재전송 공격 방지용 랜덤 값&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 요청에 nonce를 넣어서 전송했으면, 응답에도 같은 nonce가 와야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. reqPolicy(optional)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 특정 정책(policy OID)로 발급 요청&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- TSA가 지원하지 않으면 unacceptedPolicy 에러 발생 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. certReq(optional)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 응답에 TSA 인증서를 포함해달라는 힌트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 서버 정책/프로파일에 따라 다르게 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 전자서명&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- GTSA의 경우 TSQ에 서버인증서의 개인키로 전자서명을 해서 보내야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TimeStampResp(TSR)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TSA -&amp;gt; 클라이언트 타임스탬프 응답&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. status(PKIStatusInfo)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 처리 결과 코드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. (status가 성공이면) timeStampToken&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 대표적인 실패 케이스로는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- badAlg (해시 알고리즘 불가)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- badRequest (요청 불가/미지원)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- timeNotAvailable (시간원 문제)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- unacceptedPolicy / unacceptedExtension (지원하지 않는 정책)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- systemFailure (TSA 시스템 에러)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TSQ = 해시 + 옵션&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TSR = 상태 +(성공 시) 토큰&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>TSA</category>
      <category>RFC3161</category>
      <category>Timestamp</category>
      <category>tsa</category>
      <category>시점확인</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/226</guid>
      <comments>https://hsunnystory.tistory.com/226#entry226comment</comments>
      <pubDate>Mon, 12 Jan 2026 17:42:39 +0900</pubDate>
    </item>
    <item>
      <title>[TSA] TSA(Time-Stamp Authority)란?</title>
      <link>https://hsunnystory.tistory.com/225</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;TSA 개념&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TSA(Time-Stamp Authority)는 &quot;&lt;b&gt;어떤 데이터가 특정 시간 이전(또는 그 시각)에 존재했다.&lt;/b&gt;&quot; 는 사실을 제 3자가 증명해주는 기관이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 핵심은 &quot;내 PC 시간&quot;이 아닌 &lt;b&gt;TSA가 서명한 토큰&lt;/b&gt;으로 &quot;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;시각&lt;/span&gt;&lt;/b&gt;&quot;을 증명한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TSA 필요성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 시간만 기록하면 여러 문제가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 시간 조작 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버/PC 시간은 조작이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 전자서명 장기검증(LTV)과 연결&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서명이 인증서 폐지 전에 생성되었음을 증명할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 감사 추적&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그가 정말 그 시점에 기록된 것이 맞는지 증명할 수가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TSA 요구사항&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RFC 3161에 따르면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 신뢰 가능한 시간원(time source) 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 토큰마다 신뢰 가능한 시간값 포함(UTC 기반)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 토큰마다 고유한 SerialNumber 부여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 원문이 아닌 해시(messageImprint)만 타임스탬프&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- TSA는 타임스탬프 전용 키로 서명&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TSA는 &quot;시간 찍어주는 서버&quot;가 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;데이터를 입력으로 받아서, 데이터가 이 시각에 존재했다는 증명을 서명으로 돌려주는 제3자&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>TSA</category>
      <category>Timestamp</category>
      <category>tsa</category>
      <category>시점확인</category>
      <category>타임스탬프</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/225</guid>
      <comments>https://hsunnystory.tistory.com/225#entry225comment</comments>
      <pubDate>Tue, 6 Jan 2026 16:56:03 +0900</pubDate>
    </item>
    <item>
      <title>[Tomcat] 톰캣 catalina.out 로그 일자 별 쌓기(rotatelogs)</title>
      <link>https://hsunnystory.tistory.com/224</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;톰캣의 catalina.out 로그가 일자가 바뀌어도 일자별로 파일이 생성되지 않고 catalina.out에&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계속 append 되는 경우가 종종 있다. 주로 리눅스 시스템에서 발생하는 듯 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;${catalina.base}/bin/catalina.sh&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;374 라인 쯔음에 보면 이런 내용이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3가지 작업을 해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. catalina.out 파일 생성 주석 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 톰캣 start 시 백그라운드로 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. rotatelogs 를 이용한 일자별 로그 관리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;# &lt;/b&gt;&lt;/span&gt;touch &quot;$CATALINA_OUT&quot; &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;//&lt;/span&gt;&lt;b&gt; catalina.out 파일을 만드는 부분 주석 처리&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;if&amp;nbsp;[&amp;nbsp;&quot;$1&quot;&amp;nbsp;=&amp;nbsp;&quot;-security&quot;&amp;nbsp;]&amp;nbsp;;&amp;nbsp;then &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;[&amp;nbsp;$have_tty&amp;nbsp;-eq&amp;nbsp;1&amp;nbsp;];&amp;nbsp;then &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;echo&amp;nbsp;&quot;Using&amp;nbsp;Security&amp;nbsp;Manager&quot; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fi &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;shift &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;eval&amp;nbsp;$_NOHUP&amp;nbsp;&quot;\&quot;$_RUNJAVA\&quot;&quot;&amp;nbsp;&quot;\&quot;$CATALINA_LOGGING_CONFIG\&quot;&quot;&amp;nbsp;$LOGGING_MANAGER&amp;nbsp;&quot;$JAVA_OPTS&quot;&amp;nbsp;&quot;$CATALINA_OPTS&quot;&amp;nbsp;\ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-D$ENDORSED_PROP=&quot;\&quot;$JAVA_ENDORSED_DIRS\&quot;&quot;&amp;nbsp;\ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-classpath&amp;nbsp;&quot;\&quot;$CLASSPATH\&quot;&quot;&amp;nbsp;\ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-Djava.security.manager&amp;nbsp;\ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-Djava.security.policy==&quot;\&quot;$CATALINA_BASE/conf/catalina.policy\&quot;&quot;&amp;nbsp;\ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-Dcatalina.base=&quot;\&quot;$CATALINA_BASE\&quot;&quot;&amp;nbsp;\ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-Dcatalina.home=&quot;\&quot;$CATALINA_HOME\&quot;&quot;&amp;nbsp;\ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-Djava.io.tmpdir=&quot;\&quot;$CATALINA_TMPDIR\&quot;&quot;&amp;nbsp;\ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;org.apache.catalina.startup.Bootstrap&amp;nbsp;&quot;$@&quot;&amp;nbsp;start&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;2&amp;gt;&amp;amp;1&lt;/b&gt;&lt;/span&gt;\ // &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;백그라운드 실행&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; | /usr/sbin/rotatelogs &quot;$CATALINA_BASE&quot;/logs/catalina.%Y-%m-%d.log 86400 540 &amp;amp; // 일자별 로그 관리&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;# &lt;/b&gt;&lt;/span&gt;&amp;gt;&amp;gt; &quot;$CATALINA_OUT&quot; 2&amp;gt;&amp;amp;1 &quot;&amp;amp;&quot; // &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;주석 처리&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;else &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;eval&amp;nbsp;$_NOHUP&amp;nbsp;&quot;\&quot;$_RUNJAVA\&quot;&quot;&amp;nbsp;&quot;\&quot;$CATALINA_LOGGING_CONFIG\&quot;&quot;&amp;nbsp;$LOGGING_MANAGER&amp;nbsp;&quot;$JAVA_OPTS&quot;&amp;nbsp;&quot;$CATALINA_OPTS&quot;&amp;nbsp;\ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-D$ENDORSED_PROP=&quot;\&quot;$JAVA_ENDORSED_DIRS\&quot;&quot;&amp;nbsp;\ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-classpath&amp;nbsp;&quot;\&quot;$CLASSPATH\&quot;&quot;&amp;nbsp;\ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-Dcatalina.base=&quot;\&quot;$CATALINA_BASE\&quot;&quot;&amp;nbsp;\ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-Dcatalina.home=&quot;\&quot;$CATALINA_HOME\&quot;&quot;&amp;nbsp;\ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-Djava.io.tmpdir=&quot;\&quot;$CATALINA_TMPDIR\&quot;&quot;&amp;nbsp;\ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;org.apache.catalina.startup.Bootstrap &quot;$@&quot; start &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;2&amp;gt;&amp;amp;1&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;\ //&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;백그라운드 실행&amp;nbsp;&lt;/span&gt;&lt;/b&gt; &lt;br /&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;| /usr/sbin/rotatelogs &quot;$CATALINA_BASE&quot;/logs/catalina.%Y-%m-%d.log 86400 540 &amp;amp; // 일자별 로그 관리&lt;/span&gt;&lt;/b&gt; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;#&lt;/b&gt;&lt;/span&gt; &amp;gt;&amp;gt; &quot;$CATALINA_OUT&quot; 2&amp;gt;&amp;amp;1 &quot;&amp;amp;&quot; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;//&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;주석 처리&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>Web Programming/Tomcat</category>
      <category>catalina.out</category>
      <category>rotatelogs</category>
      <category>로그 일자별 관리</category>
      <category>톰캣 로그</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/224</guid>
      <comments>https://hsunnystory.tistory.com/224#entry224comment</comments>
      <pubDate>Tue, 15 Oct 2024 15:33:13 +0900</pubDate>
    </item>
    <item>
      <title>[Java] 강한 결합 vs 약한 결합(의존성 주입)</title>
      <link>https://hsunnystory.tistory.com/223</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;강한 결합&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 간의 의존 관계에서 직접 객체를 생성 하는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 생성부터 메모리 관리를 위한 소멸까지 해당 객체의 라이프 사이클을 개발자가 다 관리해야함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public void test1(){&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; Test t1 = new Test(); // &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;강한 결합 - 직접 생성&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;}&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;약한 결합&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 간의 의존 관계에서 이미 생성한 객체를 주입 받는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 사용하기만 하면 되므로 개발자가 관리할 것이 적음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public void test2(Test t){ // &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;약한 결합 - 생선된 것을 주입 받음(의존성 주입)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; Test t2 = t;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;의존성 주입(DI)을 통해 약한 결합을 사용하면 다른 클래스의 변화에 더욱 안전하고 유연하게 대처할 수 있음&lt;/b&gt;&lt;/p&gt;</description>
      <category>Programming/Java</category>
      <category>Dependency Injection</category>
      <category>DI</category>
      <category>강한결합</category>
      <category>약한결합</category>
      <category>의존성주입</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/223</guid>
      <comments>https://hsunnystory.tistory.com/223#entry223comment</comments>
      <pubDate>Mon, 14 Oct 2024 13:32:16 +0900</pubDate>
    </item>
    <item>
      <title>[Tomcat] 콘솔 한글 깨짐 증상 해결</title>
      <link>https://hsunnystory.tistory.com/222</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;톰캣을 실행하면 콘솔에서 다음과 같이 한글이 깨지는 경우가 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1468&quot; data-origin-height=&quot;766&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btM3ZF/btsEiVrrdba/TUteQ2ozT7jyNQepzcf3lK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btM3ZF/btsEiVrrdba/TUteQ2ozT7jyNQepzcf3lK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btM3ZF/btsEiVrrdba/TUteQ2ozT7jyNQepzcf3lK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtM3ZF%2FbtsEiVrrdba%2FTUteQ2ozT7jyNQepzcf3lK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1468&quot; height=&quot;766&quot; data-origin-width=&quot;1468&quot; data-origin-height=&quot;766&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 인코딩의 문제로 Windows Console의 한국어 인코딩의 경우 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;EUC-KR&lt;/b&gt;&lt;/span&gt; 이지만 톰캣의 인코딩의 경우 기본 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;UTF-8&lt;/b&gt;&lt;/span&gt;로 설정되어 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;톰캣 설치경로의 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;conf/logging.properties&lt;/span&gt;&lt;/b&gt; 파일을 보면 아래와 같은 설정부분이 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1057&quot; data-origin-height=&quot;100&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mhOOg/btsEb9ZH6DD/N31LEhiFneOUlDsqi5mlz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mhOOg/btsEb9ZH6DD/N31LEhiFneOUlDsqi5mlz0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mhOOg/btsEb9ZH6DD/N31LEhiFneOUlDsqi5mlz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmhOOg%2FbtsEb9ZH6DD%2FN31LEhiFneOUlDsqi5mlz0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1057&quot; height=&quot;100&quot; data-origin-width=&quot;1057&quot; data-origin-height=&quot;100&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UTF-8 인코딩을 EUC-KR로 수정해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;java.util.logging.ConsoleHandler.encoding = &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;EUC-KR&lt;/b&gt;&lt;/span&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Web Programming/Tomcat</category>
      <category>Console 깨짐</category>
      <category>Console 한글</category>
      <category>Tomcat console</category>
      <category>콘솔 한글</category>
      <category>톰캣 한글 깨짐</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/222</guid>
      <comments>https://hsunnystory.tistory.com/222#entry222comment</comments>
      <pubDate>Thu, 1 Feb 2024 10:47:01 +0900</pubDate>
    </item>
    <item>
      <title>[Gitlab]Gitlab 명령어 정리</title>
      <link>https://hsunnystory.tistory.com/221</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Gitlab 시작&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gitlab-ctl start&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Gitlab 종료&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gitlab-ctl stop&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. Gitlab 재시작&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gitlab-ctl restart&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. Gitlab 설정 적용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gitlab-ctl reconfigure&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 서비스 상태 확인&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;gitlab-ctl status&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;6. gitlab 로그 확인&lt;/h3&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;공통 로그&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;tail -f&amp;nbsp;&amp;nbsp;/var/log/gitlab/gitlab-rails/production.log&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;서비스 로그&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;gitlab-ctl tail &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;서비스명&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;ex) gitlab-ctl tail nginx&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Git</category>
      <category>gitlab 명령어</category>
      <category>gitlab-ctl</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/221</guid>
      <comments>https://hsunnystory.tistory.com/221#entry221comment</comments>
      <pubDate>Mon, 10 Apr 2023 14:44:53 +0900</pubDate>
    </item>
    <item>
      <title>[Gitlab] gitlab-ctl reconfigure 시 selinux 에러</title>
      <link>https://hsunnystory.tistory.com/220</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;gitlab.rb 파일 등 gitlab 관련 설정을 변경하고 gitlab-ctl reconfigure을 시도 하는데 아래와 같은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러 메세지가 출력 되는 경우가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff; background-color: #9d9d9d;&quot;&gt;Running&amp;nbsp;handlers: &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #ffffff; background-color: #9d9d9d;&quot;&gt;There&amp;nbsp;was&amp;nbsp;an&amp;nbsp;error&amp;nbsp;running&amp;nbsp;gitlab-ctl&amp;nbsp;reconfigure: &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #ffffff; background-color: #9d9d9d;&quot;&gt;execute[semodule&amp;nbsp;-i&amp;nbsp;/opt/gitlab/embedded/selinux/rhel/7/gitlab-13.5.0-gitlab-shell.pp]&amp;nbsp;(gitlab::selinux&amp;nbsp;line&amp;nbsp;32)&amp;nbsp;had&amp;nbsp;an&amp;nbsp;error:&amp;nbsp;Mixlib::ShellOut::ShellCommandFailed:&amp;nbsp;Expected&amp;nbsp;process&amp;nbsp;to&amp;nbsp;exit&amp;nbsp;with&amp;nbsp;[0],&amp;nbsp;but&amp;nbsp;received&amp;nbsp;'1' &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #ffffff; background-color: #9d9d9d;&quot;&gt;----&amp;nbsp;Begin&amp;nbsp;output&amp;nbsp;of&amp;nbsp;semodule&amp;nbsp;-i&amp;nbsp;/opt/gitlab/embedded/selinux/rhel/7/gitlab-13.5.0-gitlab-shell.pp&amp;nbsp;---- &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #ffffff; background-color: #9d9d9d;&quot;&gt;STDOUT: &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #ffffff; background-color: #9d9d9d;&quot;&gt;STDERR:&amp;nbsp;libsemanage.semanage_pipe_data:&amp;nbsp;Child&amp;nbsp;process&amp;nbsp;/usr/libexec/selinux/hll/pp&amp;nbsp;failed&amp;nbsp;with&amp;nbsp;code:&amp;nbsp;255.&amp;nbsp;(No&amp;nbsp;such&amp;nbsp;file&amp;nbsp;or&amp;nbsp;directory). &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #ffffff; background-color: #9d9d9d;&quot;&gt;gitlab-13.5.0-gitlab-shell:&amp;nbsp;libsepol.policydb_read:&amp;nbsp;policydb&amp;nbsp;module&amp;nbsp;version&amp;nbsp;19&amp;nbsp;does&amp;nbsp;not&amp;nbsp;match&amp;nbsp;my&amp;nbsp;version&amp;nbsp;range&amp;nbsp;4-17 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #ffffff; background-color: #9d9d9d;&quot;&gt;gitlab-13.5.0-gitlab-shell:&amp;nbsp;libsepol.sepol_module_package_read:&amp;nbsp;invalid&amp;nbsp;module&amp;nbsp;in&amp;nbsp;module&amp;nbsp;package&amp;nbsp;(at&amp;nbsp;section&amp;nbsp;0) &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #ffffff; background-color: #9d9d9d;&quot;&gt;gitlab-13.5.0-gitlab-shell:&amp;nbsp;Failed&amp;nbsp;to&amp;nbsp;read&amp;nbsp;policy&amp;nbsp;package &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #ffffff; background-color: #9d9d9d;&quot;&gt;libsemanage.semanage_direct_commit:&amp;nbsp;Failed&amp;nbsp;to&amp;nbsp;compile&amp;nbsp;hll&amp;nbsp;files&amp;nbsp;into&amp;nbsp;cil&amp;nbsp;files. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #ffffff; background-color: #9d9d9d;&quot;&gt;&amp;nbsp;(No&amp;nbsp;such&amp;nbsp;file&amp;nbsp;or&amp;nbsp;directory). &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #ffffff; background-color: #9d9d9d;&quot;&gt;semodule:&amp;nbsp;&amp;nbsp;Failed! &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #ffffff; background-color: #9d9d9d;&quot;&gt;----&amp;nbsp;End&amp;nbsp;output&amp;nbsp;of&amp;nbsp;semodule&amp;nbsp;-i&amp;nbsp;/opt/gitlab/embedded/selinux/rhel/7/gitlab-13.5.0-gitlab-shell.pp&amp;nbsp;---- &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #ffffff; background-color: #9d9d9d;&quot;&gt;Ran&amp;nbsp;semodule&amp;nbsp;-i&amp;nbsp;/opt/gitlab/embedded/selinux/rhel/7/gitlab-13.5.0-gitlab-shell.pp&amp;nbsp;returned&amp;nbsp;1&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크게 비중있게 다룰 내용은 아니라 간단히 요약하자면 그놈의 selinux가 문제다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;selinux 패키지에 누락된 부분이 있어서 발생하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;yum install libsemanage-static libsemanage-devel&lt;/p&gt;</description>
      <category>Git</category>
      <category>gitlab 13.5.0 reconfigure</category>
      <category>libsemanage-devel</category>
      <category>libsemanage-static</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/220</guid>
      <comments>https://hsunnystory.tistory.com/220#entry220comment</comments>
      <pubDate>Mon, 27 Mar 2023 17:29:30 +0900</pubDate>
    </item>
    <item>
      <title>[Gitlab] Centos7에 Gitlab-CE 구축</title>
      <link>https://hsunnystory.tistory.com/215</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;사전작업&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 방화벽 해제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;firewall-cmd --permanent --add-service=http &lt;br /&gt;firewall-cmd --permanent --add-service=https &lt;br /&gt;systemctl reload firewalld&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;GItlab 설치&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. GitLab package repository 추가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;curl&amp;nbsp;-sS&amp;nbsp;&lt;a href=&quot;https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.rpm.sh&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.rpm.sh&lt;/a&gt;&amp;nbsp;|&amp;nbsp;sudo&amp;nbsp;bash&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. package 설치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sudo EXTERNAL_URL=&quot;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;사용할 도메인 주소:port&lt;/b&gt;&lt;/span&gt;&quot; yum install -y gitlab-ce&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) test.gitlab.co.kr 의 도메인에 18081 포트를 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$ sudo EXTERNAL_URL=&quot;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;test.gitlab.co.kr:18080&lt;/b&gt;&lt;/span&gt;&quot; yum install -y gitlab-ce&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;==&amp;gt; 설치 후 추후 gitlab.rb에서 external_url 으로 변경 가능&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 실행 및 로그인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상적으로 설치가 완료되었다면 설치 시 입력한 EXTERNAL_URL의 정보를 브라우저에 입력하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 페이지가 로드 될 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;551&quot; data-origin-height=&quot;498&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/seOfO/btr5TqHbSjC/pp4x6ji1lbOKbzPqcRnk31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/seOfO/btr5TqHbSjC/pp4x6ji1lbOKbzPqcRnk31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/seOfO/btr5TqHbSjC/pp4x6ji1lbOKbzPqcRnk31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FseOfO%2Fbtr5TqHbSjC%2Fpp4x6ji1lbOKbzPqcRnk31%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;551&quot; height=&quot;498&quot; data-origin-width=&quot;551&quot; data-origin-height=&quot;498&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시나 아래와 같이 502 Whoops, GitLab is taking too much time to respond. 에러가 뜬다면 여유를 가지고 1~2분 정도 기다리면 접속이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 서비스가 정상적으로 모두 올라가지 않아서 그런 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;555&quot; data-origin-height=&quot;608&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDrZ9b/btr6nfD3oWz/fOiGeH19nnRZEVzHTvpzkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDrZ9b/btr6nfD3oWz/fOiGeH19nnRZEVzHTvpzkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDrZ9b/btr6nfD3oWz/fOiGeH19nnRZEVzHTvpzkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDrZ9b%2Fbtr6nfD3oWz%2FfOiGeH19nnRZEVzHTvpzkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;555&quot; height=&quot;608&quot; data-origin-width=&quot;555&quot; data-origin-height=&quot;608&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치가 완료되고 나면 터미널에 아래와 같은 문구가 출력되어 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9d9d9d; color: #ffffff;&quot;&gt;Notes: &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #9d9d9d; color: #ffffff;&quot;&gt;Default&amp;nbsp;admin&amp;nbsp;account&amp;nbsp;has&amp;nbsp;been&amp;nbsp;configured&amp;nbsp;with&amp;nbsp;following&amp;nbsp;details: &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #9d9d9d; color: #ffffff;&quot;&gt;Username:&amp;nbsp;root &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #9d9d9d; color: #ffffff;&quot;&gt;Password:&amp;nbsp;You&amp;nbsp;didn't&amp;nbsp;opt-in&amp;nbsp;to&amp;nbsp;print&amp;nbsp;initial&amp;nbsp;root&amp;nbsp;password&amp;nbsp;to&amp;nbsp;STDOUT. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #9d9d9d; color: #ffffff;&quot;&gt;Password&amp;nbsp;stored&amp;nbsp;to&amp;nbsp;/etc/gitlab/initial_root_password.&amp;nbsp;This&amp;nbsp;file&amp;nbsp;will&amp;nbsp;be&amp;nbsp;cleaned&amp;nbsp;up&amp;nbsp;in&amp;nbsp;first&amp;nbsp;reconfigure&amp;nbsp;run&amp;nbsp;after&amp;nbsp;24&amp;nbsp;hours.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 비밀번호는 /etc/gitlab/initial_root_password에 저장되어 있으며 이 파일은 처음 설정을 적용한 후 24시간 뒤에 사라진다는 내용이다. 해당 파일은 내용은 아래 처럼 적혀있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9d9d9d; color: #ffffff;&quot;&gt;#&amp;nbsp;WARNING:&amp;nbsp;This&amp;nbsp;value&amp;nbsp;is&amp;nbsp;valid&amp;nbsp;only&amp;nbsp;in&amp;nbsp;the&amp;nbsp;following&amp;nbsp;conditions &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #9d9d9d; color: #ffffff;&quot;&gt;#&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;1.&amp;nbsp;If&amp;nbsp;provided&amp;nbsp;manually&amp;nbsp;(either&amp;nbsp;via&amp;nbsp;`GITLAB_ROOT_PASSWORD`&amp;nbsp;environment&amp;nbsp;variable&amp;nbsp;or&amp;nbsp;via&amp;nbsp;`gitlab_rails['initial_root_password']`&amp;nbsp;setting&amp;nbsp;in&amp;nbsp;`gitlab.rb`,&amp;nbsp;it&amp;nbsp;was&amp;nbsp;provided&amp;nbsp;before&amp;nbsp;database&amp;nbsp;was&amp;nbsp;seeded&amp;nbsp;for&amp;nbsp;the&amp;nbsp;first&amp;nbsp;time&amp;nbsp;(usually,&amp;nbsp;the&amp;nbsp;first&amp;nbsp;reconfigure&amp;nbsp;run). &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #9d9d9d; color: #ffffff;&quot;&gt;#&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2.&amp;nbsp;Password&amp;nbsp;hasn't&amp;nbsp;been&amp;nbsp;changed&amp;nbsp;manually,&amp;nbsp;either&amp;nbsp;via&amp;nbsp;UI&amp;nbsp;or&amp;nbsp;via&amp;nbsp;command&amp;nbsp;line. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #9d9d9d; color: #ffffff;&quot;&gt;# &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #9d9d9d; color: #ffffff;&quot;&gt;#&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;If&amp;nbsp;the&amp;nbsp;password&amp;nbsp;shown&amp;nbsp;here&amp;nbsp;doesn't&amp;nbsp;work,&amp;nbsp;you&amp;nbsp;must&amp;nbsp;reset&amp;nbsp;the&amp;nbsp;admin&amp;nbsp;password&amp;nbsp;following&amp;nbsp;&lt;a style=&quot;background-color: #9d9d9d; color: #ffffff;&quot; href=&quot;https://docs.gitlab.com/ee/security/reset_user_password.html#reset-your-root-password.&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.gitlab.com/ee/security/reset_user_password.html#reset-your-root-password.&lt;/a&gt; &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #9d9d9d; color: #ffffff;&quot;&gt;Password:&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;uOZjs2M979UjfpZYyh1CRozf9AHZYbF4oVE0Tf4dU= (초기 root 패스워드)&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #9d9d9d; color: #ffffff;&quot;&gt;#&amp;nbsp;NOTE:&amp;nbsp;This&amp;nbsp;file&amp;nbsp;will&amp;nbsp;be&amp;nbsp;automatically&amp;nbsp;deleted&amp;nbsp;in&amp;nbsp;the&amp;nbsp;first&amp;nbsp;reconfigure&amp;nbsp;run&amp;nbsp;after&amp;nbsp;24&amp;nbsp;hours.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 필자의 경우 이 초기 root 패스워드로 로그인을 시도해봐도 안되서 수동으로 root 패스워드를 재설정하였다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://hsunnystory.tistory.com/217&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://hsunnystory.tistory.com/217&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1679903986694&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Gitlab] 사용자 비밀번호 변경(root 계정 포함)&quot; data-og-description=&quot;UI, Rake task, Rails console, Users API를 이용하여 사용자 패스워드를 변경할 수 있다. 이 글에서는 Rails console를 이용한 방법에 대해 포스팅 한다. 1. Rails console 열기 sudo gitlab-rails console 2. 변경할 사용자 &quot; data-og-host=&quot;hsunnystory.tistory.com&quot; data-og-source-url=&quot;https://hsunnystory.tistory.com/217&quot; data-og-url=&quot;https://hsunnystory.tistory.com/217&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bcxVSq/hyR4f4x8cu/MMZMEzxWWWi6AiKMUWpLz1/img.png?width=747&amp;amp;height=164&amp;amp;face=0_0_747_164,https://scrap.kakaocdn.net/dn/buDw8p/hyR4b18cQz/Tzua2fvxdtJ8dnEhusjLbK/img.png?width=747&amp;amp;height=164&amp;amp;face=0_0_747_164&quot;&gt;&lt;a href=&quot;https://hsunnystory.tistory.com/217&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hsunnystory.tistory.com/217&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bcxVSq/hyR4f4x8cu/MMZMEzxWWWi6AiKMUWpLz1/img.png?width=747&amp;amp;height=164&amp;amp;face=0_0_747_164,https://scrap.kakaocdn.net/dn/buDw8p/hyR4b18cQz/Tzua2fvxdtJ8dnEhusjLbK/img.png?width=747&amp;amp;height=164&amp;amp;face=0_0_747_164');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Gitlab] 사용자 비밀번호 변경(root 계정 포함)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;UI, Rake task, Rails console, Users API를 이용하여 사용자 패스워드를 변경할 수 있다. 이 글에서는 Rails console를 이용한 방법에 대해 포스팅 한다. 1. Rails console 열기 sudo gitlab-rails console 2. 변경할 사용자&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hsunnystory.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인 후 아래와 같이 나온다면 설치는 정상적으로 완료&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;487&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/stl5R/btr5YUOJ2eT/KwGrLUo7ki61NX1aIg9qGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/stl5R/btr5YUOJ2eT/KwGrLUo7ki61NX1aIg9qGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/stl5R/btr5YUOJ2eT/KwGrLUo7ki61NX1aIg9qGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fstl5R%2Fbtr5YUOJ2eT%2FKwGrLUo7ki61NX1aIg9qGk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;487&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;487&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://hsunnystory.tistory.com/218&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://hsunnystory.tistory.com/218&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1679904493280&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Gitlab] SSL 인증서 적용(HTTPS 수동 설정)&quot; data-og-description=&quot;Gitlab에 ssl을 적용하는 방법은 크게 2가지가 있다. 1. Let&amp;rsquo;s Encrypt 사용 2. SSL 인증서 수동 적용 이 글에서는 2. SSL 인증서 수동 적용에 관련하여 포스팅 한다. /etc/gitlab/gitlab.rb 파일에서 아래의 설정&quot; data-og-host=&quot;hsunnystory.tistory.com&quot; data-og-source-url=&quot;https://hsunnystory.tistory.com/218&quot; data-og-url=&quot;https://hsunnystory.tistory.com/218&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/lpxBT/hyR325dhv3/EKYZ12Is8iu3qCoZB0RO0K/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/gEbH1/hyR4gCpPSl/QZXtdtzGcC5VolyrtDVS6K/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://hsunnystory.tistory.com/218&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hsunnystory.tistory.com/218&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/lpxBT/hyR325dhv3/EKYZ12Is8iu3qCoZB0RO0K/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/gEbH1/hyR4gCpPSl/QZXtdtzGcC5VolyrtDVS6K/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Gitlab] SSL 인증서 적용(HTTPS 수동 설정)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Gitlab에 ssl을 적용하는 방법은 크게 2가지가 있다. 1. Let&amp;rsquo;s Encrypt 사용 2. SSL 인증서 수동 적용 이 글에서는 2. SSL 인증서 수동 적용에 관련하여 포스팅 한다. /etc/gitlab/gitlab.rb 파일에서 아래의 설정&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hsunnystory.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://hsunnystory.tistory.com/216&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://hsunnystory.tistory.com/216&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1679904514611&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Gitlab] ERROR 422 The change you requested was rejected. 해결&quot; data-og-description=&quot;Gitlab 구축 또는 설정 값을 변경 한 후 다음과 같은 에러 페이지가 나오는 경우가 있다. HTTP 상태 코드 422 는 422 Unprocessable Entity 이 응답은 서버가 요청을 이해하고 요청 문법도 올바르지만 요청된&quot; data-og-host=&quot;hsunnystory.tistory.com&quot; data-og-source-url=&quot;https://hsunnystory.tistory.com/216&quot; data-og-url=&quot;https://hsunnystory.tistory.com/216&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/CHmDa/hyR4fwIfmi/GBLvp4E5bpY6kvR1GhJoo0/img.png?width=682&amp;amp;height=839&amp;amp;face=0_0_682_839,https://scrap.kakaocdn.net/dn/d9KxfK/hyR32KVb6b/kwGr0FszpKykm6TNPMSIj0/img.png?width=682&amp;amp;height=839&amp;amp;face=0_0_682_839,https://scrap.kakaocdn.net/dn/G3B56/hyR4bgLdNF/bmwRDDJhwFIMZSI9duff6K/img.png?width=682&amp;amp;height=839&amp;amp;face=0_0_682_839&quot;&gt;&lt;a href=&quot;https://hsunnystory.tistory.com/216&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hsunnystory.tistory.com/216&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/CHmDa/hyR4fwIfmi/GBLvp4E5bpY6kvR1GhJoo0/img.png?width=682&amp;amp;height=839&amp;amp;face=0_0_682_839,https://scrap.kakaocdn.net/dn/d9KxfK/hyR32KVb6b/kwGr0FszpKykm6TNPMSIj0/img.png?width=682&amp;amp;height=839&amp;amp;face=0_0_682_839,https://scrap.kakaocdn.net/dn/G3B56/hyR4bgLdNF/bmwRDDJhwFIMZSI9duff6K/img.png?width=682&amp;amp;height=839&amp;amp;face=0_0_682_839');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Gitlab] ERROR 422 The change you requested was rejected. 해결&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Gitlab 구축 또는 설정 값을 변경 한 후 다음과 같은 에러 페이지가 나오는 경우가 있다. HTTP 상태 코드 422 는 422 Unprocessable Entity 이 응답은 서버가 요청을 이해하고 요청 문법도 올바르지만 요청된&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hsunnystory.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://hsunnystory.tistory.com/219&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://hsunnystory.tistory.com/219&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1679904520683&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Gitlab] Repository 변경(운영 중 포함)&quot; data-og-description=&quot;1. gitlab 서비스 중지 gitlab-ctl stop 2. gitlab.rb 수정 위와 같이 git_data_dirs 설정 부분에 path 설정 git_data_dirs({ &amp;quot;default&amp;quot; =&amp;gt; { &amp;quot;path&amp;quot; =&amp;gt; &amp;quot;데이터를 저장할 경로&amp;quot; } }) 3. 수정 사항 반영 gitlab-ctl reconfigure 4. gitlab &quot; data-og-host=&quot;hsunnystory.tistory.com&quot; data-og-source-url=&quot;https://hsunnystory.tistory.com/219&quot; data-og-url=&quot;https://hsunnystory.tistory.com/219&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cosWeW/hyR385qcTK/nhGEJ1rhkNOP5337mDga30/img.png?width=286&amp;amp;height=94&amp;amp;face=0_0_286_94,https://scrap.kakaocdn.net/dn/boqWv3/hyR34207bZ/Gk4A6TGkreUcktnkXf1Rkk/img.png?width=286&amp;amp;height=94&amp;amp;face=0_0_286_94&quot;&gt;&lt;a href=&quot;https://hsunnystory.tistory.com/219&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hsunnystory.tistory.com/219&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cosWeW/hyR385qcTK/nhGEJ1rhkNOP5337mDga30/img.png?width=286&amp;amp;height=94&amp;amp;face=0_0_286_94,https://scrap.kakaocdn.net/dn/boqWv3/hyR34207bZ/Gk4A6TGkreUcktnkXf1Rkk/img.png?width=286&amp;amp;height=94&amp;amp;face=0_0_286_94');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Gitlab] Repository 변경(운영 중 포함)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1. gitlab 서비스 중지 gitlab-ctl stop 2. gitlab.rb 수정 위와 같이 git_data_dirs 설정 부분에 path 설정 git_data_dirs({ &quot;default&quot; =&amp;gt; { &quot;path&quot; =&amp;gt; &quot;데이터를 저장할 경로&quot; } }) 3. 수정 사항 반영 gitlab-ctl reconfigure 4. gitlab&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hsunnystory.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://hsunnystory.tistory.com/220&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://hsunnystory.tistory.com/220&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1679905986256&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Gitlab] gitlab-ctl reconfigure 시 selinux 에러&quot; data-og-description=&quot;gitlab.rb 파일 등 gitlab 관련 설정을 변경하고 gitlab-ctl reconfigure을 시도 하는데 아래와 같은 에러 메세지가 출력 되는 경우가 있다. Running handlers: There was an error running gitlab-ctl reconfigure: execute[semodule &quot; data-og-host=&quot;hsunnystory.tistory.com&quot; data-og-source-url=&quot;https://hsunnystory.tistory.com/220&quot; data-og-url=&quot;https://hsunnystory.tistory.com/220&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dcsOOx/hyR36T6xHV/Ek8cchLGORuRMYUr7K6GDk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/p7BaA/hyR31Fhppg/Qb8IdFDi9duGBgRgQvaYMK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://hsunnystory.tistory.com/220&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hsunnystory.tistory.com/220&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dcsOOx/hyR36T6xHV/Ek8cchLGORuRMYUr7K6GDk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/p7BaA/hyR31Fhppg/Qb8IdFDi9duGBgRgQvaYMK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Gitlab] gitlab-ctl reconfigure 시 selinux 에러&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;gitlab.rb 파일 등 gitlab 관련 설정을 변경하고 gitlab-ctl reconfigure을 시도 하는데 아래와 같은 에러 메세지가 출력 되는 경우가 있다. Running handlers: There was an error running gitlab-ctl reconfigure: execute[semodule&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hsunnystory.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 : &lt;span&gt;Centos7 Gitlab Community Edition 설치 가이드&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;https://about.gitlab.com/install/#centos-7&quot;&gt;https://about.gitlab.com/install/#centos-7&lt;/a&gt;&lt;/p&gt;</description>
      <category>Git</category>
      <category>gitlab 구축</category>
      <category>gitlab-ce</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/215</guid>
      <comments>https://hsunnystory.tistory.com/215#entry215comment</comments>
      <pubDate>Mon, 27 Mar 2023 17:07:06 +0900</pubDate>
    </item>
    <item>
      <title>[Gitlab] Repository 변경(운영 중 포함)</title>
      <link>https://hsunnystory.tistory.com/219</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. gitlab 서비스 중지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gitlab-ctl stop&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. gitlab.rb 수정&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;286&quot; data-origin-height=&quot;94&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cWXbMx/btr6oH7KrWQ/InLScGstmHZZUcp0AMJbO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cWXbMx/btr6oH7KrWQ/InLScGstmHZZUcp0AMJbO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cWXbMx/btr6oH7KrWQ/InLScGstmHZZUcp0AMJbO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcWXbMx%2Fbtr6oH7KrWQ%2FInLScGstmHZZUcp0AMJbO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;286&quot; height=&quot;94&quot; data-origin-width=&quot;286&quot; data-origin-height=&quot;94&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 git_data_dirs 설정 부분에 path 설정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;git_data_dirs({ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;default&quot; =&amp;gt; { &quot;path&quot; =&amp;gt; &quot;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;데이터를 저장할 경로&lt;/b&gt;&lt;/span&gt;&quot; } &lt;br /&gt;&amp;nbsp;})&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 수정 사항 반영&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gitlab-ctl reconfigure&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. gitlab 서비스 시작&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gitlab-ctl start&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;* 혹시 이미 운영중인데 Repository를 변경해야 하는 경우 기존 데이터를 복사 후 새로운 경로에 복사 후 그 경로를 지정해주면 된다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>Git</category>
      <category>gitlab repository</category>
      <category>gitlab 레포지토리 변경</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/219</guid>
      <comments>https://hsunnystory.tistory.com/219#entry219comment</comments>
      <pubDate>Mon, 27 Mar 2023 16:09:42 +0900</pubDate>
    </item>
    <item>
      <title>[Gitlab] SSL 인증서 적용(HTTPS 수동 설정)</title>
      <link>https://hsunnystory.tistory.com/218</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Gitlab에 ssl을 적용하는 방법은 크게 2가지가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Let&amp;rsquo;s Encrypt 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. SSL 인증서 수동 적용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 &lt;b&gt;2. SSL 인증서 수동 적용&lt;/b&gt;에 관련하여 포스팅 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;/etc/gitlab/gitlab.rb&lt;/span&gt;&lt;/b&gt; 파일에서 아래의 설정을 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. HTTP 요청을 HTTPS로 리다이렉트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nginx['redirect_http_to_https']&amp;nbsp;=&amp;nbsp;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;true&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 사용할 도메인 URL 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;external_url '&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;사용할 URL&lt;/b&gt;&lt;/span&gt;'&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. SSL 인증서 경로 지정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nginx['ssl_certificate'] = &quot;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;crt 파일의 절대경로&lt;/b&gt;&lt;/span&gt;&quot; &lt;br /&gt;nginx['ssl_certificate_key'] = &quot;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;key 파일의 절대경로&lt;/span&gt;&lt;/b&gt;&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. SSL 인증서 패스워드 정보(Key에 비밀번호가 설정되어 있는 경우)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nginx['ssl_password_file'] = '&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;key 비밀번호가 있는 파일의 절대경로&lt;/span&gt;&lt;/b&gt;'&amp;nbsp; &amp;nbsp;--&amp;gt; ex) txt 파일&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 설정 값 적용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sudo&amp;nbsp;gitlab-ctl&amp;nbsp;reconfigure&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Git</category>
      <category>crt 파일</category>
      <category>https</category>
      <category>key 파일</category>
      <category>ssl 수동</category>
      <category>SSL 인증서</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/218</guid>
      <comments>https://hsunnystory.tistory.com/218#entry218comment</comments>
      <pubDate>Tue, 28 Feb 2023 17:33:57 +0900</pubDate>
    </item>
    <item>
      <title>[Gitlab] 사용자 비밀번호 변경(root 계정 포함)</title>
      <link>https://hsunnystory.tistory.com/217</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;UI, Rake task, Rails console, Users API를 이용하여 사용자 패스워드를 변경할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 Rails console를 이용한 방법에 대해 포스팅 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Rails console 열기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sudo&amp;nbsp;gitlab-rails&amp;nbsp;console&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;747&quot; data-origin-height=&quot;164&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8KKAp/btrZ8NnlMNV/gGJHzg6kiKpEOpzXFrb48K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8KKAp/btrZ8NnlMNV/gGJHzg6kiKpEOpzXFrb48K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8KKAp/btrZ8NnlMNV/gGJHzg6kiKpEOpzXFrb48K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8KKAp%2FbtrZ8NnlMNV%2FgGJHzg6kiKpEOpzXFrb48K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;747&quot; height=&quot;164&quot; data-origin-width=&quot;747&quot; data-origin-height=&quot;164&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 변경할 사용자 찾기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;user = User.find_by_username '&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;사용자이름&lt;/b&gt;&lt;/span&gt;'&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. &lt;span style=&quot;background-color: #fdfdfd; color: #000000;&quot;&gt;user.password 및 user.password_confirmation에 대한 값을 설정하여 암호 재설정&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;new_password = '&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;변경할 패스워드&lt;/span&gt;&lt;/b&gt;' &lt;br /&gt;user.password&amp;nbsp;=&amp;nbsp;new_password &lt;br /&gt;user.password_confirmation&amp;nbsp;=&amp;nbsp;new_password&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 저장&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;user.save!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. Rails Console 종료&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;exit&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처 : &lt;a href=&quot;https://docs.gitlab.com/ee/security/reset_user_password.html#reset-your-root-password&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.gitlab.com/ee/security/reset_user_password.html#reset-your-root-password&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1676958831662&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Reset a user&quot; data-og-description=&quot;Documentation for GitLab Community Edition, GitLab Enterprise Edition, Omnibus GitLab, and GitLab Runner.&quot; data-og-host=&quot;docs.gitlab.com&quot; data-og-source-url=&quot;https://docs.gitlab.com/ee/security/reset_user_password.html#reset-your-root-password&quot; data-og-url=&quot;https://docs.gitlab.com/ee/security/reset_user_password.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.gitlab.com/ee/security/reset_user_password.html#reset-your-root-password&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.gitlab.com/ee/security/reset_user_password.html#reset-your-root-password&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Reset a user&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Documentation for GitLab Community Edition, GitLab Enterprise Edition, Omnibus GitLab, and GitLab Runner.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.gitlab.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Git</category>
      <category>gitlab root 패스워드</category>
      <category>gitlab 비밀번호 변경</category>
      <category>gitlab 사용자 비밀번호 변경</category>
      <category>root 비밀번호</category>
      <author>H.Sunny,,</author>
      <guid isPermaLink="true">https://hsunnystory.tistory.com/217</guid>
      <comments>https://hsunnystory.tistory.com/217#entry217comment</comments>
      <pubDate>Tue, 21 Feb 2023 15:07:46 +0900</pubDate>
    </item>
  </channel>
</rss>