<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>hansol_choi.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Thu, 23 Oct 2025 14:48:19 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>hansol_choi.log</title>
            <url>https://velog.velcdn.com/images/hansol_choi/profile/ef307150-7f35-403f-a963-ffc0bb9fe90d/social_profile.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. hansol_choi.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/hansol_choi" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Service TestCoverage]]></title>
            <link>https://velog.io/@hansol_choi/Service-TestCoverage</link>
            <guid>https://velog.io/@hansol_choi/Service-TestCoverage</guid>
            <pubDate>Thu, 23 Oct 2025 14:48:19 GMT</pubDate>
            <description><![CDATA[<h3 id="요약본">요약본</h3>
<blockquote>
<p>첫번째, 
ServiceTest 클래스 상단에 <strong>모키토 선언</strong>,
안에 테스트 대상인 <strong>service에 @InjectMocks 선언</strong>,
테스트 시 주입될 클래스는 <strong>레포지토리이므로 @Mock 선언</strong>,</p>
</blockquote>
<blockquote>
<p>두번째,
이후에는 각 메서드 마다 단위테스트를 만드는데 이때,
(예를들어 create)
<strong>AAA패턴</strong>에 맞게 <strong>given, when, then</strong>.
given으로 <strong>입력될 데이터</strong>를 미리 대본처럼 만들고,
when으로 <strong>서비스의 메서드를 호출</strong>하고,
then의 <strong>assert/verify</strong>를 이용하여 <strong>결과값/메서드가 잘 반환/실행 되었는지</strong> <strong>테스트</strong>한다.</p>
</blockquote>
<hr>
<h3 id="핵심-개념">핵심 개념</h3>
<blockquote>
<p>기본 세팅 핵심 개념 3개</p>
</blockquote>
<ol>
<li>Mock : 가짜 객체</li>
<li>Stubbing : 대본 써주는 것(이 메서드가 호출되면 이렇게 답해!)</li>
<li>Verify(검증) : 호출 자체를 확인(정말 그 메서드를 그 횟수만큼 불럿나?)</li>
</ol>
<ul>
<li><code>@ExtendWith(MockitoExtension.class)</code> : 모키토 준비 완료!
클래스 상단에 선언해줘야 한다.</li>
<li><code>@Mock</code> : 가짜 객체 표시, ServiceTest 단에서는 Repository가 가짜로 만들어진다.</li>
<li><code>@InjectMocks</code> : 진짜로 테스트할 대상, Service를 테스트할 예정이므로 </li>
</ul>
<pre><code class="language-java">@InjectMocks
BoardService boardService;</code></pre>
<hr>
<blockquote>
<p>AAA패턴 : 테스트의 뼈대</p>
</blockquote>
<ol>
<li>Given(준비) : 입력 데이터 만들기</li>
<li>When(실행) : 서비스 메서드 호출</li>
<li>Then(검증) : 리턴값 검사(assert...) + 레포지토리 호출 검사(verify...)</li>
</ol>
<hr>
<h3 id="내가-사용한-문법-정리">내가 사용한 문법 정리</h3>
<ol>
<li><p>스텁(BDD 스타일)</p>
<pre><code class="language-java">given(mock.메서드(인자))).willReturn(값);
given(mock.메서드(인자))).willThrow(예외);</code></pre>
</li>
<li><p>검증(호출 확인)</p>
<pre><code class="language-java">verify(mock, times(1)).메서드(인자);
verify(mock, never()).메서드(...);
verifyNoMoreInteractions(mock); &lt;= 더 이상 건드리지 않았는지.</code></pre>
</li>
<li><p>리턴 값 검사</p>
<pre><code class="language-java">assertNotNull(x);
assertEquals(a, b);
assertThrows(예외.class, ()-&gt; 실행);</code></pre>
</li>
<li><p>매처 (인자 아무거나/정확히)</p>
<pre><code class="language-java">any(Board.class)
anyString()
eq(&quot;고정값&quot;)</code></pre>
</li>
<li><p>void 메서드 예외 스텁(save 처럼 리턴 있으면, given...로 충분하지만 리턴 없는 void는 </p>
<pre><code class="language-java">doThrow(new SomeEx()).when(mock).voidMethod(args);</code></pre>
</li>
</ol>
<hr>
<blockquote>
<p>내가 작성한 Create Test </p>
</blockquote>
<pre><code class="language-java">Users users = mock(Users.class);
given(usersRepository.findByUsername(&quot;tester&quot;)).willReturn(Optional.of(users));
given(users.getUserId()).willReturn(10L);</code></pre>
<ul>
<li>Users는 DB 엔티티라 가짜로 만들었고</li>
<li>서비스가 tester로 유저를 조회하면 있다고 응답</li>
<li>그 유저의 id는 10L라고 대본을 줌</li>
</ul>
<pre><code class="language-java">given(boardRepository.existsByTitle(&quot;제목&quot;)).willReturn(false);</code></pre>
<ul>
<li>제목 중복 검사 결과: 중복 아님(false) -&gt; 그래서 save까지 진행 가능</li>
</ul>
<pre><code class="language-java">given(boardRepository.save(any(Board.class))).willReturn(
    new Board(1L, 10L, &quot;제목&quot;, &quot;내용&quot;, LocalDateTiem.now(), LocalDateTime.now())
);</code></pre>
<ul>
<li>실제 저장은 안 하지만 save 부르면 이런 엔티티가 나온다라고 가짜 결과를 줌</li>
</ul>
<pre><code class="language-java">BoardCreateDto result = boardService.create(boardCreateCommand, &quot;tester&quot;);</code></pre>
<ul>
<li>여기서만 진짜 로직이 실행 됨(서비스 메서드)</li>
<li>내부에서 위의 스텁들을 쭉 호출하게 됨</li>
</ul>
<pre><code class="language-java">assertNotNull(result);
assertEquals(&quot;제목&quot;, result.getTitle());
assertEquals(&quot;내용&quot;, result.getContent());</code></pre>
<ul>
<li>리턴 DTO가 제대로 나왔는지 확인</li>
</ul>
<pre><code class="language-java">verify(usersRepository, times(1)).findByUsername(&quot;tester&quot;);
verify(boardRepository, times(1)).existsByTitle(&quot;제목&quot;);
verify(boardRepository, times(1)).save(any(Board.class));</code></pre>
<ul>
<li>서비스 흐름이 우리가 의도한 순서대로 레포지토리를 불렀는지 확인</li>
</ul>
<p>여기까지가 완벽한 AAA 패턴이라고 볼 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[정적 팩토리 메서드]]></title>
            <link>https://velog.io/@hansol_choi/%EC%A0%95%EC%A0%81-%ED%8C%A9%ED%86%A0%EB%A6%AC-%EB%A9%94%EC%84%9C%EB%93%9C</link>
            <guid>https://velog.io/@hansol_choi/%EC%A0%95%EC%A0%81-%ED%8C%A9%ED%86%A0%EB%A6%AC-%EB%A9%94%EC%84%9C%EB%93%9C</guid>
            <pubDate>Sun, 05 Oct 2025 12:47:37 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>핵심: 객체를 만들 때부터 그 용도나 상태를 지정해주는 것</p>
</blockquote>
<pre><code class="language-java">원래였던 것
User user = new User(&quot;한솔&quot;,20);

정적 팩터리 메서드
User guest = User.guest(); // 비회원 상태로 초기화된 객체
User admin = User.admin(); // 관리자 권한을 가진 객체</code></pre>
<blockquote>
<p>정적 팩토리 메서드 원칙
        1. static 키워드 사용
        2. 내부에서 new로 객체 생성 후 반환
        3. 메서드명이 의미를 설명함
        4. 타입 매개변수 <code>&lt;T&gt;</code> 지원 (제네릭)</p>
</blockquote>
<pre><code class="language-java">public static &lt;T&gt; 반환타입 메서드명(매개변수들) {
    return new 생성자호출(매개변수);
}
</code></pre>
<pre><code class="language-java">public static &lt;T&gt; ApiResponse&lt;T&gt; of(T data) {
    return new ApiResponse&lt;&gt;(data);
}
</code></pre>
<p>public static &lt;- 정적이라 객체 없이 호출이 가능하다(ApiResponse.OK() 형태)
return new AipResponse&lt;&gt;() &lt;- factory 역할(객체를 만들어서 반환)
즉 생성자를 감싸서 좀 더 명확한 이름과 제어를 부여한 형태</p>
<p>요약하자면 
정적 팩토리 메서드는 객체 생성을 대신 수행하는 정적(static)메서드다.
생성자보다 의미 있는 이름을 줄 수 있고 캐싱이나 싱글톤 처리 등 객체 생성을 통제하기에 좋다.
지금 코드의 OK()와 fail()이 그 예시다.</p>
<hr>
<blockquote>
<p>예시 코드</p>
</blockquote>
<pre><code class="language-java">public class User{
    private String name;
    private String role;

    private User(String name, String role){
        this.name=name;
        this.role=role;
    }

    // 정적 팩터리 메서드
    public static User guest(){ 
        return new User(&quot;GUEST&quot;,&quot;GUEST&quot;);
    }
    public static User admin(){
        return new User(&quot;ADMIN&quot;,&quot;ADMIN&quot;);
    }
    public static User of(String name){ // static은 객체 없이도 호출이 가능하다는 점.
        return new User(name,&quot;USER&quot;); //User.of(&quot;한솔&quot;)이렇게 클래스 이름으로 바로 호출이 가능하다. 생성자처럼 new 안 써도 됨

        // of: ~로부터 객체를 만들어낸다.
        // 즉 User객체를 만들어서 돌려주겠다는 뜻
        // 표준 라이브러리에서도 많이 씀
        // List.of(), Optional.of() 등
    }
}</code></pre>
<p>사용 시</p>
<pre><code class="language-java">User u1 = User.guest(); // 이미 게스트 상태로 만들어짐
User u2 = User.admin(); // 이미 관리자 상태로 만들어짐</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[JVM,GC란?]]></title>
            <link>https://velog.io/@hansol_choi/JVMGC%EB%9E%80</link>
            <guid>https://velog.io/@hansol_choi/JVMGC%EB%9E%80</guid>
            <pubDate>Tue, 30 Sep 2025 02:54:56 GMT</pubDate>
            <description><![CDATA[<p>주제: JVM, GC가 뭐고 — 왜 자바로 개발하냐?</p>
<hr>
<h1 id="jvm">JVM</h1>
<p>Java Virual Machine은 자바 바이트코드(.class)를 운영체제/CPU와 상관없이 실행해 주는 가상 컴퓨터.</p>
<p>내 노트북이든 EC2리눅스든 같은 .jar를 돌릴 수 있게 해 주는 번역기+실행기</p>
<h5 id="자바-프로그램을-실행시키는-가상-컴퓨터">자바 프로그램을 실행시키는 가상 컴퓨터</h5>
<h3 id="jvm이-하는-일">JVM이 하는 일</h3>
<ul>
<li><p>바이트코드 실행</p>
<ul>
<li><p>자바 코드를 .java → javac 컴파일 → .class 바이트코드</p>
</li>
<li><p>JVM은 이 바이트코드를 읽어서 OS/CPU가 이해할 수 있는 명령으로 변환</p>
</li>
</ul>
</li>
<li><p>메모리 관리</p>
<ul>
<li><p>JVM은 프로그램 실행에 필요한 메모리를 나눠서 관리</p>
<ul>
<li><p>Heap 영역: 객체 저장</p>
</li>
<li><p>Stack 영역: 메서드 호출, 지역변수 저장</p>
</li>
<li><p>Method 영역: 클래스 코드, 메서드, 상수 저장</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<pre><code class="language-javascript">MyCode.java ──(javac 컴파일)──&gt; MyCode.class(바이트코드)
                         ↓
                    JVM이 읽음
     [클래스 로더] → [실행 엔진(인터프리터+JIT)] → [OS/CPU]
                         │
                      [메모리 관리(GC)]
</code></pre>
<p>JVM 안의 핵심 부품(이름 정도만 기억하자)</p>
<h4 id="클래스-로더">클래스 로더</h4>
<p>.class 파일을 JVM메모리로 적재</p>
<h4 id="실행엔진">실행엔진</h4>
<ul>
<li>인터프리터: 바이트코드를 한 줄씩 읽어 즉시 싱행(빠른 시작)</li>
<li>JIP 컴파일러: 자주 실행되는 코드를 기계어로 바꿔 캐시해 둠(따듯해질수록 빨리 사라짐)</li>
</ul>
<h4 id="런타임-메모리-구역">런타임 메모리 구역</h4>
<ul>
<li><p>스택(stack): 메서드 호출/ 지역변수(원시타입 int, long등). 매우 빠르지만 짧게 씀</p>
<ul>
<li>너무 깊게 재귀 돌리면 stackOverFlowError</li>
</ul>
</li>
<li><p>힙(Heep): new로 만든 객체들이 오래 사는 공간, GC가 치워줌</p>
<ul>
<li>다 차면 OutOfMemoryError: Java heap space</li>
</ul>
</li>
<li><p>메타스페이스(Metaspace): 클래스의 설계도 정보</p>
<ul>
<li>이게 차면 OutOfMemoryError: Metaspace</li>
</ul>
</li>
</ul>
<blockquote>
<p>포인트</p>
</blockquote>
<ul>
<li>스택=짧고 빠름</li>
<li>힙=객체가 모여 사는 곳</li>
<li>GC=힙 청소부</li>
</ul>
<hr>
<h1 id="gc가비지-컬렉션">GC(가비지 컬렉션)</h1>
<p>GC는 더 이상 쓰지 않는 객체(어느 곳에서도 참조하지 않음)를 자동으로 찾아서 힙에서 채워주는 기능
메모리 안정성, 개발속도가 향상된다.</p>
<h3 id="기본-동작-원리진짜-핵심만">기본 동작 원리(진짜 핵심만)</h3>
<ul>
<li><p>“GC 루트(실행 중인 스레드 스택, static 변수 등)”에서 닿을 수 없는 객체는 쓰레기다 → 청소</p>
</li>
<li><p>청소할 때는 잠깐 stop-the-world(모든 스레드 멈춤) 구간이 꼭 생김</p>
</li>
<li><p>세대별(Generational) GC:
Young(금방 생긴 객체; 금방 죽음) / Old(오래 사는 객체)로 나눠 효율적으로 청소</p>
<ul>
<li>Minor GC: Young만 빠르게 치움</li>
<li>Major/Full GC: Old 포함 큰 청소 (보통 더 무거움)</li>
</ul>
</li>
</ul>
<h4 id="gc-종류이름만-익히기">GC 종류(이름만 익히기)</h4>
<ul>
<li><p>Serial/Parallel: 옛날 방식, 단순/스루풋 지향</p>
</li>
<li><p>G1 GC: 요즘 기본값(자바 9+ 핫스팟)—적당히 빠르고 멈춤시간 짧게 만들려고 고안됨</p>
</li>
<li><p>ZGC / Shenandoah: 아주 짧은 멈춤시간이 필요할 때(고급/특수 상황)</p>
</li>
</ul>
<blockquote>
<p>현업 기본값: 그냥 G1 쓰고 힙 크기만 맞추면 90%는 충분합니다.</p>
</blockquote>
<hr>
<h1 id="왜-자바로-프로그래밍할까">왜 자바로 프로그래밍할까?</h1>
<h3 id="장점">장점</h3>
<ul>
<li><p>한 번 빌드하면 어디서나 실행(JVM 덕분) — 윈도우/리눅스/ECS/EC2 가리지 않음</p>
</li>
<li><p>GC로 메모리 안전성 확보 — 메모리 해제 실수로 뻗을 일이 적음</p>
</li>
<li><p>성능 안정성 — JIT로 뜨거워질수록 빠름, 서버에서 충분히 고성능</p>
</li>
<li><p>거대한 생태계 — Spring Boot, Maven/Gradle, 라이브러리, 예제가 넘침</p>
</li>
<li><p>채용/학습 자료 풍부 — 팀 꾸리기, 유지보수, 문서, 커뮤니티가 매우 강함</p>
</li>
<li><p>장기 지원(LTS) — JDK 17/21 같은 장기 버전으로 안정적 운영</p>
</li>
</ul>
<h3 id="단점">단점</h3>
<ul>
<li><p>GC 멈춤이 아예 0은 아님(튜닝으로 줄임)</p>
</li>
<li><p>메모리 사용량이 비교적 큼</p>
</li>
<li><p>“처음 기동”이 스크립트 언어보다 느릴 수 있음(하지만 서버는 오래 떠 있음)</p>
</li>
</ul>
<blockquote>
<p>결론: 서비스를 오래, 안정적으로, 많은 사람이 협업해야 한다? → 자바/스프링은 매우 안전한 선택.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Java Collection Framework (JCF)]]></title>
            <link>https://velog.io/@hansol_choi/Java-Collection-Framework-JCF</link>
            <guid>https://velog.io/@hansol_choi/Java-Collection-Framework-JCF</guid>
            <pubDate>Tue, 30 Sep 2025 02:25:09 GMT</pubDate>
            <description><![CDATA[<p>Java Collection Framework
    컴퓨터 공학 자료 구조 과목 : List , Set , Map, Stack, Queue란?</p>
<hr>
<pre><code class="language-java">public static void main(String[] args) {
        exampleList();
        exampleSet();
        exampleMap();
        exampleStackUsingDeque();
        exampleQueueUsignDeque();
        examplePriorityQueue();
    }</code></pre>
<p>각 메서드를 만들어서 컬렉션에 대해 다뤄보도록 하겠다.</p>
<p>이번 목표는 개념을 이해하고 상황에 맞게 적절한 활용 예시이다.</p>
<hr>
<h1 id="list">list</h1>
<h3 id="에시-쇼핑몰-장바구니-기능">에시: 쇼핑몰 장바구니 기능</h3>
<pre><code class="language-java">private static void exampleList() {
        System.out.println(&quot;-------List start-------&quot;);

        List&lt;String&gt; list = new ArrayList&lt;&gt;();
        list.add(&quot;zebra&quot;);
        list.add(&quot;apple&quot;);
        list.add(&quot;monkey&quot;);
        list.add(&quot;banana&quot;);
        list.add(&quot;cherry&quot;); //중복 가능, 순서 o
        System.out.println(&quot;////List :&quot; + list);
        System.out.println(&quot;////List 1번째 :&quot; + list.get(1));
        // 삽입
        list.add(1, &quot;X&quot;);
        System.out.println(&quot;////List 1삽입 :&quot; + list); // 결과: 삽입하고 뒤에 밀림
        // 삭제
        list.remove(2);
        System.out.println(&quot;////List 2삭제 :&quot; + list);
        System.out.println();
    }</code></pre>
<blockquote>
<p>why?
 사용자가 같은 상품을 두 번 담을 수 있다(중복 허용)
 첫 번째 담은 상품처럼 인덱스로 접근이 가능해야 한다.</p>
</blockquote>
<pre><code class="language-java">List&lt;String&gt; cart = new ArrayList&lt;&gt;();
cart.add(&quot;노트북&quot;);
cart.add(&quot;마우스&quot;);
cart.add(&quot;노트북&quot;); // 중복 허용
System.out.println(&quot;장바구니: &quot; + cart); // [노트북, 마우스, 노트북]
System.out.println(&quot;첫 번째 상품: &quot; + cart.get(0)); // 노트북</code></pre>
<h1 id="set">Set</h1>
<h3 id="에시-콘서트-예매-서비스에서-좌석-번호를-관리할-경우-같은-좌석이-중복-예약되면-안됨">에시: 콘서트 예매 서비스에서 좌석 번호를 관리할 경우, 같은 좌석이 중복 예약되면 안됨</h3>
<pre><code class="language-java">    private static void exampleSet() {
        System.out.println(&quot;=== Set (HashSet / LinkedHashSet / TreeSet) ===&quot;);

        // HashSet: 가장 흔함, 빠름, &#39;순서 없음&#39;
        Set&lt;String&gt; hashSet = new HashSet&lt;&gt;();
        hashSet.add(&quot;B&quot;);
        hashSet.add(&quot;A&quot;);
        hashSet.add(&quot;B&quot;); // 중복은 무시
        System.out.println(&quot;HashSet (순서X) = &quot; + hashSet);

        // LinkedHashSet: &#39;입력 순서 유지&#39;
        Set&lt;String&gt; linkedSet = new LinkedHashSet&lt;&gt;();
        linkedSet.add(&quot;B&quot;);
        linkedSet.add(&quot;A&quot;);
        linkedSet.add(&quot;B&quot;); // 중복 무시
        System.out.println(&quot;LinkedHashSet (입력순서 유지) = &quot; + linkedSet); // [B, A]

        // TreeSet: &#39;자동 정렬&#39; (기본 오름차순), null 불가
        Set&lt;String&gt; treeSet = new TreeSet&lt;&gt;();
        treeSet.add(&quot;B&quot;);
        treeSet.add(&quot;A&quot;);
        treeSet.add(&quot;C&quot;);
        System.out.println(&quot;TreeSet (정렬됨) = &quot; + treeSet); // [A, B, C]

        // 언제 쓰나: 중복을 자동으로 제거해야 할 때(유니크한 값 모으기: 이메일, 태그 등)
        // 주의: HashSet은 출력 순서가 예측 불가 → 화면 표시용이면 LinkedHashSet 사용
        System.out.println();
    }
</code></pre>
<blockquote>
<p>why?
중복 자동 제거, 순서 필요없다는 특징</p>
</blockquote>
<pre><code class="language-java">Set&lt;String&gt; seats = new HashSet&lt;&gt;();
seats.add(&quot;A1&quot;);
seats.add(&quot;A2&quot;);
seats.add(&quot;A1&quot;); // 중복 -&gt; 무시됨

System.out.println(&quot;예약된 좌석: &quot; + seats); // [A1, A2]</code></pre>
<p>입력 순서를 유지해야 한다면 LinkedHashSet
좌석 번호를 자동 정렬하려면 TreeSet을 사용하면 된다.</p>
<hr>
<pre><code class="language-java">    private static void exampleMap() {
        System.out.println(&quot;=== Map (HashMap / LinkedHashMap / TreeMap) ===&quot;);

        // HashMap: 빠름, 순서 없음
        Map&lt;String, Integer&gt; hashMap = new HashMap&lt;&gt;();
        hashMap.put(&quot;apple&quot;, 3);   // 키 &quot;apple&quot; → 값 3
        hashMap.put(&quot;banana&quot;, 1);
        hashMap.put(&quot;apple&quot;, 10);  // 같은 키에 put → 기존 값 3을 10으로 &#39;덮어쓰기&#39;
        System.out.println(&quot;HashMap size = &quot; + hashMap.size());         // 2
        System.out.println(&quot;HashMap get(&#39;apple&#39;) = &quot; + hashMap.get(&quot;apple&quot;)); // 10

        // LinkedHashMap: 입력 순서 유지(화면 표시용에 좋음)
        Map&lt;String, Integer&gt; linkedMap = new LinkedHashMap&lt;&gt;();
        linkedMap.put(&quot;apple&quot;, 3);
        linkedMap.put(&quot;banana&quot;, 1);
        linkedMap.put(&quot;cherry&quot;, 2);
        System.out.println(&quot;LinkedHashMap (입력순서 유지) = &quot; + linkedMap); // {apple=3, banana=1, cherry=2}

        // TreeMap: 키 기준 정렬(사전순 등)
        Map&lt;String, Integer&gt; treeMap = new TreeMap&lt;&gt;();
        treeMap.put(&quot;banana&quot;, 1);
        treeMap.put(&quot;apple&quot;, 3);
        treeMap.put(&quot;cherry&quot;, 2);
        System.out.println(&quot;TreeMap (키 정렬) = &quot; + treeMap); // {apple=3, banana=1, cherry=2}

        // Map 순회: entrySet()으로 키와 값을 함께 꺼내면 편함
        System.out.println(&quot;Iterate LinkedHashMap:&quot;);
        for (Map.Entry&lt;String, Integer&gt; e : linkedMap.entrySet()) {
            System.out.println(&quot;  &quot; + e.getKey() + &quot; -&gt; &quot; + e.getValue());
        }

        // 언제 쓰나: 기준(키)로 빠르게 찾아야 할 때(아이디→회원정보, 상품코드→상품)
        // 주의: 같은 키 put 시 덮어쓰기, HashMap은 순서X, TreeMap은 null 키 불가
        System.out.println();
    }</code></pre>
<ul>
<li>학생 이름으로 점수를 관리할 경우, 이름(Key)-&gt;점수(value)</li>
</ul>
<blockquote>
<p>why?
키를 통해 빠르게 값 검색, 같은 이름이 들어오면 점수 덮어씀</p>
</blockquote>
<blockquote>
<p><strong>단, 동명이인일 경우에는??</strong>
-&gt; Map은Key가 유일해야 한다는 규칙이 있다.
그래서 HashMap, TreeMap, LinkedHashMep 전부 동일한 키가 들어오면 기존 값을 덮어쓴다.
그래서 동명이인일 경우에는 이름을 Key로 쓰지 않고 학번, ID같은 고유 식별자를 Key로 두면 된다.</p>
</blockquote>
<hr>
<h1 id="stack">Stack</h1>
<h3 id="에시-텍스트-편집기에서-실행-취소-기능-구현-시">에시: 텍스트 편집기에서 실행 취소 기능 구현 시</h3>
<pre><code class="language-java">     // - 레거시 java.util.Stack 대신 Deque(ArrayDeque) 권장
    private static void exampleStackUsingDeque() {
        System.out.println(&quot;=== Stack (using Deque/ArrayDeque) ===&quot;);

        // Deque을 스택처럼 사용: push/pop/peek
        Deque&lt;Integer&gt; stack = new ArrayDeque&lt;&gt;();
        stack.push(1); // 맨 위에 1
        stack.push(2); // 맨 위에 2
        stack.push(3); // 맨 위에 3 (최상단)

        System.out.println(&quot;pop() -&gt; &quot; + stack.pop());  // 3 (가장 나중에 넣은 것)
        System.out.println(&quot;peek() -&gt; &quot; + stack.peek()); // 2 (위에 무엇이 있는지 보기만)

        // 언제 쓰나: 실행취소(UNDO), 역순 처리, 괄호검사 등 LIFO 로직
        // 왜 Deque? Stack은 동기화 등 오버헤드가 있고 레거시라 비권장. Deque가 더 가볍고 빠름.
        System.out.println();
    }
</code></pre>
<blockquote>
<p>why?
  마지막 동작부터 취소해야 함(LIFO)</p>
</blockquote>
<pre><code class="language-java"> Deque&lt;String&gt; undoStack = new ArrayDeque&lt;&gt;();
undoStack.push(&quot;글자 입력&quot;);
undoStack.push(&quot;굵게 적용&quot;);
undoStack.push(&quot;밑줄 적용&quot;);

System.out.println(&quot;실행 취소: &quot; + undoStack.pop()); // 밑줄 적용 취소
System.out.println(&quot;다음 취소: &quot; + undoStack.peek()); // 굵게 적용 (보기만)</code></pre>
<hr>
<h1 id="queue">Queue</h1>
<h3 id="예시-은행-창구-대기-시스템-먼저-온-손님이-먼저-처리되어야-함">예시: 은행 창구 대기 시스템, 먼저 온 손님이 먼저 처리되어야 함</h3>
<pre><code class="language-java">    // - 보통 Deque(ArrayDeque)로 큐 연산(offer/poll/peek)
    private static void exampleQueueUsingDeque() {
        System.out.println(&quot;=== Queue (using Deque/ArrayDeque) ===&quot;);

        Deque&lt;String&gt; queue = new ArrayDeque&lt;&gt;();
        queue.offer(&quot;A&quot;); // 뒤로 A
        queue.offer(&quot;B&quot;); // 뒤로 B
        queue.offer(&quot;C&quot;); // 뒤로 C

        System.out.println(&quot;poll() -&gt; &quot; + queue.poll()); // A (가장 먼저 들어온 것)
        System.out.println(&quot;peek() -&gt; &quot; + queue.peek()); // B (앞에 무엇이 있는지 보기만)

        // 언제 쓰나: 작업 대기열, BFS, 생산자-소비자(동시성 환경에서는 BlockingQueue 계열 사용)
        System.out.println();
    }</code></pre>
<blockquote>
<p>why?
FIFO구조가 딱 맞음</p>
</blockquote>
<pre><code class="language-java">Deque&lt;String&gt; waitingQueue = new ArrayDeque&lt;&gt;();
waitingQueue.offer(&quot;손님1&quot;);
waitingQueue.offer(&quot;손님2&quot;);
waitingQueue.offer(&quot;손님3&quot;);

System.out.println(&quot;처리: &quot; + waitingQueue.poll()); // 손님1
System.out.println(&quot;다음 대기: &quot; + waitingQueue.peek()); // 손님2</code></pre>
<h1 id="priorityqueue">PriorityQueue</h1>
<h3 id="예시-고객센터에서-vip고객-문의--일반-고객문의-순으로-처리해야-할-경우">예시: 고객센터에서 VIP고객 문의-&gt; 일반 고객문의 순으로 처리해야 할 경우</h3>
<pre><code class="language-java">        System.out.println(&quot;=== PriorityQueue (기본 오름차순) ===&quot;);

        // 작은 값이 먼저 나온다(기본 자연 순서)
        Queue&lt;Integer&gt; pq = new PriorityQueue&lt;&gt;();
        pq.offer(5);
        pq.offer(1);
        pq.offer(3);

        // poll()할 때마다 가장 &#39;작은&#39; 값부터 빠짐 → 1, 3, 5
        System.out.print(&quot;poll order = &quot;);
        while (!pq.isEmpty()) {
            System.out.print(pq.poll() + &quot; &quot;);
        }
        System.out.println();

        // 내림차순 우선순위가 필요하면 Comparator 넘기기
        Queue&lt;Integer&gt; maxPq = new PriorityQueue&lt;&gt;(Comparator.reverseOrder());
        maxPq.offer(5);
        maxPq.offer(1);
        maxPq.offer(3);
        System.out.print(&quot;reverse poll order = &quot;);
        while (!maxPq.isEmpty()) {
            System.out.print(maxPq.poll() + &quot; &quot;); // 5, 3, 1
        }
        System.out.println();

        // 언제 쓰나: “중요한 것 먼저 처리” (예: 가장 높은 점수, 가장 긴 대기시간 등)
        System.out.println();
    }
}</code></pre>
<blockquote>
<p>why?
우선순위 높은 요소 먼저 꺼내야 함</p>
</blockquote>
<pre><code class="language-java">class Customer {
    String name;
    int priority; // 숫자가 작을수록 우선순위 높음
    Customer(String name, int priority) {
        this.name = name;
        this.priority = priority;
    }
    public String toString() { return name; }
}

PriorityQueue&lt;Customer&gt; pq = new PriorityQueue&lt;&gt;(Comparator.comparingInt(c -&gt; c.priority));
pq.offer(new Customer(&quot;일반 고객&quot;, 5));
pq.offer(new Customer(&quot;VIP 고객&quot;, 1));
pq.offer(new Customer(&quot;일반 고객2&quot;, 3));

while (!pq.isEmpty()) {
    System.out.println(&quot;응대: &quot; + pq.poll()); // VIP → 일반 고객2 → 일반 고객
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[배포 이후 정리]]></title>
            <link>https://velog.io/@hansol_choi/%EB%B0%B0%ED%8F%AC-%EC%9D%B4%ED%9B%84-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@hansol_choi/%EB%B0%B0%ED%8F%AC-%EC%9D%B4%ED%9B%84-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Mon, 29 Sep 2025 01:10:15 GMT</pubDate>
            <description><![CDATA[<h1 id="목적">목적</h1>
<p>내가 이번에 할 시나리오는 임시 레포를 CICD를 이용하여 서버에 배포하는 법을 익힐 것이다.
지난 시간에 기존 팀프로젝트를 <strong>AWS에 EC2를 이용하여 수동으로 배포해본 적이 있다.</strong>
<strong>성공적</strong>이었고 이제 <strong>CICD으로 전환해 주면 되는데 이건 내가 배운 적이 없어서 test용으로 할 생각</strong>이다.</p>
<hr>
<p>새로운 인스턴스 생성하면 보안그룹을 추가해야한다.</p>
<blockquote>
<p>보안그룹 &amp; SSH 접근</p>
</blockquote>
<ul>
<li><strong>SSH (22)</strong>: 기본 열려 있음 (터미널 접속)</li>
<li><strong>HTTP (80)</strong>: 웹 요청</li>
<li><strong>사용자 정의 TCP (8080)</strong>: 스프링 서버 실행 시</li>
</ul>
<p>접근 시 명령어는 </p>
<pre><code class="language-java">ssh -i &quot;C:\Users\user\Desktop\.ssh\hansolKey.pem&quot; ec2-user@퍼블릭IP</code></pre>
<blockquote>
<p>이제 배포할 프로젝트에 맞게 세팅을 해줘야 한다.</p>
</blockquote>
<h3 id="1-java-설치">1. Java 설치</h3>
<pre><code class="language-javascript">sudo dnf install -y java-17-amazon-corretto</code></pre>
<p>AL2023에선 <code>amazon-corretto</code> 패키지 사용해야 한다.</p>
<pre><code class="language-javascript">// 출력예시
openjdk version &quot;17.0.16&quot; 2025-07-15 LTS
OpenJDK Runtime Environment Corretto-17.0.16.8.1 (build 17.0.16+8-LTS)
OpenJDK 64-Bit Server VM Corretto-17.0.16.8.1 (build 17.0.16+8-LTS, mixed mode, sharing)</code></pre>
<p>버전을 확인해 주면 위 코드와 같이 나온다.</p>
<h3 id="2-mysql">2. MySQL</h3>
<p>내가 사용한 DB는 MySQL이다.</p>
<p>먼저 OS 확인해 줘야 한다.</p>
<pre><code class="language-javascript"> cat /etc/os-release</code></pre>
<p>다음으로 해줘야 할 건 MySQL 저장소 등록이다.</p>
<pre><code class="language-javascript"> sudo rpm -Uvh https://repo.mysql.com/mysql80-community-release-el9-1.noarch.rpm</code></pre>
<p>이제 설치 및 실행을 진행하면 된다.</p>
<pre><code class="language-javascript">sudo dnf install -y mysql-community-server</code></pre>
<pre><code class="language-javascript">sudo systemctl enable --now mysqld
systemctl status mysqld --no-pager</code></pre>
<pre><code class="language-javascript"> -u root -p</code></pre>
<p>이때 비밀번호를 넣으면 된다.
입력 시 아무것도 안 보이는 게 당연하다.</p>
<p>그러나 에러가 났다.</p>
<pre><code class="language-java">ERROR 1045 (28000): Access denied for user &#39;root&#39;@&#39;localhost&#39; (using password: YES)</code></pre>
<p>임시 비밀번호를 받자</p>
<pre><code class="language-java"> sudo grep -n &quot;temporary password&quot; /var/log/mysqld.log | tail -1</code></pre>
<p>이렇게 치면 코드가 한 줄 나오는데, 맨 끝에 코드를 암기하자.</p>
<pre><code class="language-java">r!Lwso-f70PU</code></pre>
<p>나같은 경우 위처럼 생긴 임시 비밀번호를 받았다.</p>
<pre><code class="language-java">mysql -u root -p</code></pre>
<p>다시 이 명령어를 쳐서 임시 비밀번호를 넣으면 MySQL에 접근이 가능해진다.
접근 성공했으니 비밀번호를 반드시 변경하자.</p>
<pre><code class="language-mysql">ALTER USER &#39;root&#39;@&#39;localhost&#39; IDENTIFIED BY &#39;새비밀번호&#39;;
FLUSH PRIVILEGES;</code></pre>
<p>변경을 완료했으면 정상적으로 변경 했는지 확인해야한다.</p>
<p>MySQL에서 빠져나와서 다시</p>
<pre><code class="language-java">mysql -u root -p</code></pre>
<p>를 입력하여 변경한 비밀번호를 입력하면 된다.</p>
<p>다음으로 할 건 스키마 생성이다.</p>
<p>내 프로젝트의 스키마 이름은 awsTest니까 그대로 진행하겠다.</p>
<pre><code class="language-mysql">CREATE DATABASE IF NOT EXISTS awsTest
  DEFAULT CHARACTER SET utf8mb4
  COLLATE utf8mb4_unicode_ci;
</code></pre>
<p>생성된 걸 확인하려면 아래 명령어를 치면된다.</p>
<pre><code class="language-mysql">SHOW DATABASES;</code></pre>
<h3 id="3-redis">3. redis</h3>
<p>우리 프로젝트는 redis를 사용하였으니 아마존 리눅스 역시 똑같이 설치해주면 된다.</p>
<p>어째서인지 설치가 너무 간단했다.</p>
<p>설치 명령어</p>
<pre><code class="language-java">sudo dnf install -y redis6
sudo systemctl enable --now redis6</code></pre>
<p>상태 확인</p>
<pre><code class="language-java">systemctl status redis6 --no-pager</code></pre>
<p>Ping 테스트</p>
<pre><code class="language-java">redis6-cli ping</code></pre>
<p>했더니 PONG
정상 설치 완료하였다..</p>
<h3 id="4-git">4. git</h3>
<p>깃을 설치해야 한다는 걸 빼먹어서 뒤늦게라도 설치했다.</p>
<pre><code class="language-java"># 1) git 설치
sudo dnf install -y git

# 2) 리포 내려받기 (홈에 awsTest 폴더로)
git clone https://github.com/hansolChoi29/awsTest.git ~/awsTest

cd ~/awsTest</code></pre>
<pre><code class="language-java"># gradlew에 실행권한 (안 돼도 true로 무시)
chmod +x gradlew || true

# 빌드 (빠르게 하려면 테스트 생략)
./gradlew clean bootJar -x test</code></pre>
<p>빌드 (Gradle Wrapper 사용)</p>
<pre><code class="language-java">chmod +x gradlew || true
./gradlew clean bootJar -x test</code></pre>
<p>JAR 실행</p>
<pre><code class="language-java"># 만들어진 JAR 확인
ls -al build/libs

# plain 제외한 실제 JAR 선택
JAR=$(ls build/libs/*.jar | grep -v &#39;plain&#39; | head -n 1)
echo &quot;JAR=$JAR&quot;

# 기존 프로세스 종료(있으면) 후 실행
pkill -f &quot;java.*$(basename &quot;$JAR&quot;)&quot; || true
nohup java -jar &quot;$JAR&quot; --spring.profiles.active=prod &gt; app.log 2&gt;&amp;1 &amp;

# 기동 확인 + 로그 보기
sleep 3
pgrep -f &quot;$(basename &quot;$JAR&quot;)&quot; &amp;&amp; echo &quot;App RUNNING&quot; || echo &quot;App NOT running&quot;
tail -n 200 app.log</code></pre>
<hr>
<p>잘 설치가 되었는지 최종확인</p>
<ol>
<li>자바<pre><code class="language-java">java -version</code></pre>
</li>
</ol>
<pre><code class="language-java">출력예시
penjdk version &quot;17.0.16&quot; 2025-07-15 LTS
OpenJDK Runtime Environment Corretto-17.0.16.8.1 (build 17.0.16+8-LTS)
OpenJDK 64-Bit Server VM Corretto-17.0.16.8.1 (build 17.0.16+8-LTS, mixed mode, sharing)</code></pre>
<ol start="2">
<li>MySQL</li>
</ol>
<pre><code class="language-java">systemctl status mysqld --no-pager</code></pre>
<pre><code class="language-java">출력예시 - 마지막 줄만 보면 됨
Started mysqld.service - MySQL Server.</code></pre>
<pre><code class="language-java">mysql --version</code></pre>
<pre><code class="language-java">출력예시

mysql  Ver 8.0.43 for Linux on x86_64 (MySQL Community Server - GPL)</code></pre>
<p>로그인확인</p>
<pre><code class="language-java">mysql -u root -p</code></pre>
<p>비밀번호 입력 후 데이터베이스 확인</p>
<pre><code class="language-mysql">SHOW DATABASES;</code></pre>
<pre><code class="language-mysql">출력 예시
+--------------------+
| Database           |
+--------------------+
| awsTest            |
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.05 sec)</code></pre>
<ol start="3">
<li>redis 확인</li>
</ol>
<pre><code class="language-java">systemctl status redis6 --no-pager</code></pre>
<pre><code class="language-java">출력예시 - 아래 키워드만 체크해 주면 됨
- Active: active (running) → 현재 실행 중
- Status: &quot;Ready to accept connections&quot; → 클라이언트 연결 받을 준비 완료
- Main PID: 30252 (redis6-server) → Redis 서버 프로세스 번호
- 포트: `127.0.0.1:6379` → 로컬호스트에서 6379 포트로 대기 중</code></pre>
<pre><code class="language-java">redis6-cli ping</code></pre>
<pre><code class="language-java">출력 예시
PONG</code></pre>
<hr>
<blockquote>
<p>CICD : git Action</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java] Optinal 대체 왜 쓰는데?]]></title>
            <link>https://velog.io/@hansol_choi/Java-Optinal-%EB%8C%80%EC%B2%B4-%EC%99%9C-%EC%93%B0%EB%8A%94%EB%8D%B0</link>
            <guid>https://velog.io/@hansol_choi/Java-Optinal-%EB%8C%80%EC%B2%B4-%EC%99%9C-%EC%93%B0%EB%8A%94%EB%8D%B0</guid>
            <pubDate>Fri, 18 Jul 2025 10:14:22 GMT</pubDate>
            <description><![CDATA[<p>아무리 봐도 Optional이 이해가 안 됐고 와닿지가 않아서 하루종일 이것만 공부해 보기로 했다.</p>
<blockquote>
<p>왜 이해를 못했냐면</p>
</blockquote>
<p><strong>*NPE</strong>를 예방하기 위해서라면, <strong>결국 에러 발생되게 만드는 용도</strong>아닌가?란 생각이 들었고,
에러를 임의로 발생시키도록 하는 <strong>예외처리</strong>가 따로 있는데. 
그리고 <strong>null이던 말던 왜 신경을 써야 하는지</strong>에 대해 불만이 있었다.</p>
</br>

<pre><code>NPE(nullPointerException)
아무 객체도 가리키지 않는 참조에서 메서드나 필드에 접근하려 할 때 발생하는 자바 런타임 에러.
왜 발생하는데?=&gt; 자바는 null이라는 특별 값에 대해 아무것도 없다라고 표시하는데, 
그 상태에서 length(), toUpperCase(), .charAt(0)같은 메서드를 호출하면 
자바가 어떻게 실행하지?하고 터뜨리는 에러가 NPE.</code></pre><p>그러나 Java에서는 null을 그대로 냅두게 되면, 여러문제를 일으킬 수 있다.</p>
<p>Java에서 null 참조를 그대로 두면 어디서 NPE가 터질지 모르는 문제와 중첩된 if (x != null) 처리의 복잡함을 야기한다. Optional은 ‘값 없음’을 안전하게 캡슐화해서, NPE 없이 깔끔하게 널 체크 흐름을 작성할 수 있게 도와준다.</p>
<p>위 내용을 정리해보면</p>
<ul>
<li>null이면 메서드 호출 때 NullPointerException이 발생된다.</li>
<li>이걸 일일이 if(x!=null)으로 막으면 코드가 길어진다.</li>
<li>그래서 Optional.ofNullable(str)해야 하며, 코드는 간결해진다.</li>
</ul>
<hr>
<blockquote>
<p>주의사항</p>
</blockquote>
<ul>
<li>절대 get()만 믿고 쓰지 말 것, 반드시 isPresent()나 orElse...계열로 안전하게 처리 할 것.</li>
<li>필드나 파라미터 타입으로 Optional 남발은 피하고 주로 메서드 반환용으로 사용 할 것.</li>
<li>of() vs ofNullable() 처리를 명확히 인지 할 것.</li>
</ul>
<p>위 내용에 대해 설명하고자 한다.</p>
<hr>
<p><strong>1) 절대 get() 만 믿고 쓰지 말 것 (isPresent()/ orElse.. 함께 사용 권장)</strong></p>
<ul>
<li>opt.get()은 Optional 내부에 값이 있을 때는 그 값을 꺼내주지만, 값이 없을 경우에는 호출하는 순간 NoSuchElementException이 터진다.</li>
<li>그래서 안전하게 쓰려면 아래 코드와 같다</li>
</ul>
<pre><code class="language-java">if(opt.isPresent()){
    String v=opt.get();
}</code></pre>
<ul>
<li>혹은</li>
</ul>
<pre><code class="language-java">// 값이 있으면 그대로, 없으면 기본값 반환
String v=opt.orElse(&quot;기본&quot;);</code></pre>
<pre><code class="language-java">String v=opt.orElseGet(()-&gt;&quot;기본&quot;);</code></pre>
<p>요약하자면, get() 단독 사용은 예외 위험이 크니 항상 &quot;값 있음&quot; 여부를 확인하거나 isPresent() 아니면 기본값을 지정해주는 orElse/ orElseGet 방식을 쓰라는 말이다.</p>
<p><strong>2) 필드나 파라미터 타입으로 Optional 남발은 피하고 주로 메서드 반환용으로 사용 할 것.</strong></p>
<ul>
<li>필드(클래스 변수)나 메서드 파라미터에 Optional을 쓰면 오히려 코드가 복잡해지고 직렬화나 테스트 작성 시 번거로워 진다.</li>
</ul>
<pre><code class="language-java">// 권장하지 않는 예시코드
public class User{
    private Optional&lt;String&gt; middleName;
    public void setMiddleNaem(Optional&lt;String&gt; m){...}
}</code></pre>
<ul>
<li>대신 Optional은 &quot;이 메서드는 값이 없을 수도 있을 수도 있다.&quot;를 표현하는 용도로 메서드 반환타입에만 사용하는 것이 일반적인 관례다.</li>
</ul>
<pre><code class="language-java">//권장되는 예시 코드
public Optional&lt;User&gt; findUserByEmail(String email){...}</code></pre>
<p>요약하자면, Optional을 필드, 파라미터로 쓰면 오히려 부작용이 많으니 메서드 리턴으로만 쓰고 ** <em>내부 구현이나 호출 시에는 일반 Null체크를 쓰는 걸 추천*</em>한다.</p>
<pre><code>*내부 구현이나 호출 시에는 일반 Null체크를 쓰는 걸 추천, 
일반 null체크란? 참조형 변수가 null인지 if문으로 먼저 확인한 뒤에야 안전하게 사용하는 방식이다.
예를들면 필드처리, 메서드 파라미터, 호출.

즉, 여기서 내부 구현이란, 클래스 안에서 필드나 파라미터에 Optioanl을 쓰지않고
if(param != null)/ if(field != null)으로 단순히 널 여부만 체크하는 방식을 말한다.
호출 시에만 Optional API를 사용해 주면, 인터페이스 설계와 구현이 깔끔하게 분리된다.

잠시 필드처리, 메서드 파라미터, 호출에 대해 설명해보자면 아래와 같다.</code></pre><p><strong>일반 Null 체크 1)필드 처리 예시</strong></p>
<ul>
<li><p>잘못된 예 (Optional 필드 남발)</p>
<pre><code class="language-java">public class User{
//Optional을 필드로 쓰면 직렬화·테스트·가독성이 모두 불편해짐
  private Optional&lt;String&gt; middleName;

  public User(Optional&lt;String&gt; middleName){
      this.middleName=middleName;
  }

  public Optional&lt;String&gt; getMiddleName(){
      return middleName;
  }
}</code></pre>
</li>
</ul>
<ul>
<li>권장 예(null 체크로 간단히)</li>
</ul>
<pre><code class="language-java">public class User{
    private String middleName;

    public User(String middleName){
        this.middleName=middleName != null?middleName:&quot;&quot;;
    }

    public String getMiddleName(){
        return middleName != null &amp;&amp; !middleName.isEmpty()
        ?middleName
        :&quot;정보 없음&quot;;
    }
}</code></pre>
<p><strong>일반 Null 체크 2) 메서드 파라미터 예시</strong></p>
<p>잘못된 예(Optional 파라미터)</p>
<pre><code class="language-java">public void updateName(Optional&lt;String&gt; newNameOpt){
    newNameOpt.ifPresent(n-&gt;this.name=n);
}</code></pre>
<p>권장 예(null체크 파라미터)</p>
<pre><code class="language-java">public void updateName(String newName){
//호출 시 null인지 직접 검사
    if(newName !=null &amp;&amp; !newName.isBlack()){
        this.name=newName;
    }//else: 그대로 두거나 기본 처리.
}</code></pre>
<p><strong>일반 Null 체크 3) 호출 시 예시</strong></p>
<pre><code class="language-java">// Optional 반환 메서드
public Optional&lt;User&gt; findUserById(String id){...}

//호출할 때
Optional&lt;User&gt; userOpt=repo.findUserById(id);

// -&gt;Optional API로 안전하게 처리
if(userOtp.isPresent()){
    User u=userOpt.get();
    //내부적으로는 null 체크 없이 바로 사용 가능
    System.out.println(u.getName());
}else{
    System.out.println(&quot;사용자를 찾을 수 없습니다.&quot;);
}</code></pre>
<p>일반 NULL체크에 대한 설명은 여기서 마무리하고 본론으로 돌아가자.</p>
<p><strong>3) of() vs ofNullable() 처리를 명확히 인지 할 것</strong></p>
<ol>
<li>Optional.of(value)</li>
</ol>
<ul>
<li>value가 절대 null이 아님을 보장할 때만 사용한다.</li>
<li>만약 value가 null이면 NPE발생!</li>
</ul>
<ol start="2">
<li>Optional.ofNullable(value)</li>
</ol>
<ul>
<li>value가 null일 수도 아닐 수도 있을 때 사용한다.</li>
<li>value가 null이면 빈 Optional(Optional.empty())이 생성되고</li>
<li>value가 null이 아니면, 값을 담은 Optional이 생성된다.</li>
</ul>
<p>요약하자면, null이 아님이 확실할 때 of() 사용, null 가능성이 조금이라도 있으면 ofNullable() 사용해주면 된다.</p>
<hr>
<blockquote>
<p>따라서</p>
</blockquote>
<ul>
<li>NPE을 예방하기 위해서라면, 결국 에러 발생시키는 용도 아닌가?란 생각이 들었고, ⇒ Optional은 에러 대신 “값 없음”을 반환해 주며, NPE를 아예 없애준다.</li>
<li>에러를 임의로 발생시키도록 하는 예외처리가 따로 있는데. ⇒ 예외처리는 비정상 상황 대응용, Optional은 정상 흐름에서 값 부재를 처리하는 API이다.</li>
<li>그리고 null이던 말던 왜 신경을 써야 하는지에 대해 불만이 있었다. ⇒ null 방치는 예측 불가능한 NPE를 유발하므로, Optional로 사전에 “값 없음”을 명시해 안정성을 높여야 한다.</li>
</ul>
<p>문제 풀다가 추가적으로, 이해할 필요가 있겠다란 코드가 보였다.</p>
<pre><code class="language-java">Optional&lt;String&gt; optEmpty = Optional.ofNullable(null);

// orElse는 기본값을 즉시 호출합니다.
String a = optEmpty.orElse(defaultValue());  
// &quot;기본값 생성&quot;이 즉시 출력되고 &quot;DEF&quot; 반환

// orElseGet은 기본값을 필요할 때만 호출합니다.
String b = optEmpty.orElseGet(() -&gt; defaultValue());  
// &quot;기본값 생성&quot;이 &quot;DEF&quot; 반환 시점에 출력</code></pre>
<hr>
<blockquote>
<p>예제코드</p>
</blockquote>
<pre><code class="language-java">        // 1) of() – null이 절대 아닐 때만
        Optional&lt;String&gt; optA = Optional.of(&quot;A값&quot;);
        // get단독사용한 이유: null 아님이 100%이기 때문.
        System.out.println(&quot;optA: &quot; + optA.get());  // safe, 값 있음

        // 2) ofNullable() – null 가능
        String maybeB = null;
        Optional&lt;String&gt; optB = Optional.ofNullable(maybeB);

        // 3) empty() – 빈 Optional
        Optional&lt;String&gt; optEmpty = Optional.empty();

        System.out.println(&quot;-----------&quot;);

        // 4) isPresent() + get() =&gt; null 가능성이 조금이라도 있기 때문에 
        //isPresent로 먼저 확인 후 get씀
        if (optA.isPresent()) {
            System.out.println(&quot;optA 존재: &quot; + optA.get());
        }
        if (!optB.isPresent()) {
            System.out.println(&quot;optB 비어있음&quot;);
        }

        System.out.println(&quot;-----------&quot;);

        // 5) ifPresent() – 값 있을 때만 실행
        optA.ifPresent(v -&gt; System.out.println(&quot;ifPresent optA: &quot; + v));
        optB.ifPresent(v -&gt; System.out.println(&quot;ifPresent optB: &quot; + v));  // 실행 안 됨

        System.out.println(&quot;-----------&quot;);

        // 6) orElse() – 값 없으면 기본값
        System.out.println(&quot;optB orElse: &quot; + optB.orElse(&quot;기본B&quot;));

        // 7) orElseGet() – 값 없을 때만 supplier 실행
        System.out.println(&quot;optEmpty orElseGet: &quot; +
            optEmpty.orElseGet(() -&gt; {
                System.out.println(&quot;  supplier 실행!&quot;);
                return &quot;Lazy기본&quot;;
            })
        );

        System.out.println(&quot;-----------&quot;);

        // 8) filter() + map()
        Optional&lt;String&gt; optNum = Optional.of(&quot;123&quot;);
        Optional&lt;Integer&gt; optLen = optNum
            .filter(s -&gt; s.length() &gt; 2)  // 길이 &gt; 2만 통과
            //map(s -&gt; s.length())와 map(String::length); 일치 함
            .map(String::length);         // 길이를 Integer로 매핑
        System.out.println(&quot;filter+map 결과: &quot; + optLen.orElse(0));</code></pre>
<pre><code class="language-css">// 출력결과
optA: A값
-----------
optA 존재: A값
optB 비어있음
-----------
ifPresent optA: A값
-----------
optB orElse: 기본B
  supplier 실행!
optEmpty orElseGet: Lazy기본
-----------
filter+map 결과: 3</code></pre>
<hr>
<blockquote>
<p>옵셔널 관련 문제</p>
</blockquote>
<pre><code class="language-java">// 문제 4-1: Optional 입력값 처리
// 1) Scanner로 사용자로부터 문자열 input을 한 줄 입력받으세요.
// 2) Optional.ofNullable(input)으로 Optional&lt;String&gt; opt을 생성하세요.
// 3) opt.ifPresentOrElse를 사용해
//    값이 있으면 &quot;입력값: {값}&quot;을, 없으면 &quot;값이 없습니다.&quot;를 출력하세요.

import java.util.Optional;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        // 여기에 코드 작성하세요


        sc.close();
    }
}
</code></pre>
<hr>
<pre><code class="language-java">// 문제 4-2: Optional.of() 예외 확인
// 1) Optional&lt;String&gt; optA = Optional.of(&quot;ABC&quot;); 선언
// 2) try-catch 구문 안에서 Optional.of(null)을 호출해 보세요.
// 3) catch(NullPointerException e) 블록에서
//    &quot;of(null) 호출 시 예외 발생!&quot;을 출력하세요.

import java.util.Optional;

public class Main {
    public static void main(String[] args) {
        // 여기에 코드 작성하세요
    }
}</code></pre>
<hr>
<pre><code class="language-java">// 문제 4-3: orElse vs orElseGet 비교
// 1) Optional&lt;String&gt; optEmpty = Optional.ofNullable(null); 선언
// 2) String a = optEmpty.orElse(defaultValue());
// 3) String b = optEmpty.orElseGet(() -&gt; defaultValue());
// 4) defaultValue() 메서드를 정의하고,
//    내부에서 &quot;기본값 생성&quot;을 출력한 뒤 &quot;DEF&quot;를 반환하세요.
// 5) a와 b를 차례로 출력해 보세요.

import java.util.Optional;

public class Main {
    public static void main(String[] args) {
        // 여기에 코드 작성하세요
    }

    // 여기에 defaultValue() 메서드 작성
}

//[TIP]
// orElse(): 객체에 값이 없을때 지정한 기본값을 즉시 반환
//여기서 defaultValue()메서드는 orElse()가 호출될 때 즉시 실행됨

//orElseGet() :  객체에 값이 없을 때 람다식 사용하여 기본값을 계산하는 방식
//defaultValue()는 기본값을 반환하는 메서드,값이 없을 때 대체할 기본값을 생성하는 메서드

//람다식을 사용하면, orElse()와는 달리 값이 없을 때만 defaultValue() 메서드를 실행하게 됩니다.
// 그렇기 때문에 값이 있을 경우에는 defaultValue() 메서드가 호출되지 않습니다.</code></pre>
<hr>
<pre><code class="language-java">// 문제 4-4: Optional 필터 및 매핑
// 1) Optional&lt;String&gt; emailOpt = Optional.ofNullable(&quot;user@example.com&quot;); 선언
// 2) filter(e -&gt; e.contains(&quot;@&quot;))로 검증 후
// 3) map(e -&gt; e.substring(e.indexOf(&quot;@&quot;) + 1))로 도메인만 추출하세요.
// 4) orElse(&quot;도메인 없음&quot;)으로 최종 결과를 출력하세요.

import java.util.Optional;

public class Main {
    public static void main(String[] args) {
        // 여기에 코드 작성하세요
    }
}</code></pre>
<hr>
<pre><code class="language-java">// 문제 4-5: 중첩 Optional 처리 (flatMap)
// 아래 Address, User 클래스를 정의하고
// 1) User user = new User(&quot;홍길동&quot;, null); 생성
// 2) Optional.ofNullable(user)
//       .flatMap(u -&gt; Optional.ofNullable(u.addr))
//       .map(a -&gt; a.city)
//       .orElse(&quot;주소 정보 없음&quot;)
//    를 이용해 결과를 출력하세요.

import java.util.Optional;

class Address {
    String city;
    Address(String city) { this.city = city; }
}

class User {
    String name;
    Address addr;
    User(String name, Address addr) {
        this.name = name;
        this.addr = addr;
    }
}

public class Main {
    public static void main(String[] args) {
        // 여기에 코드 작성하세요
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java] Lambda & Streams]]></title>
            <link>https://velog.io/@hansol_choi/Java-Lambda-Streams</link>
            <guid>https://velog.io/@hansol_choi/Java-Lambda-Streams</guid>
            <pubDate>Fri, 18 Jul 2025 10:00:59 GMT</pubDate>
            <description><![CDATA[<h1 id="lambda">Lambda</h1>
</br>

<p>Lambda(람다식)이란?</p>
<p>자바에서 함수를 간결하게 표현하는 문법이다. 익명클래스의 축약버전.
쉽게 말해, 함수나 메서드를 변수처럼 넘길 수 있게 해주는 문법이다.</p>
<pre><code class="language-java">(매개변수) -&gt; { 실행문 }</code></pre>
<blockquote>
<p>왜 람다를 쓰는가?</p>
</blockquote>
<ul>
<li>코드가 짧고 직관적</li>
<li>익명 객체(new Runnable() {...})보다 간결함</li>
<li>함수형 인터페이스와 함께 사용하여 전달/실행 구조 가능</li>
</ul>
<pre><code class="language-java">// 기존 방식
Runnable r = new Runnable() {
    public void run() {
        System.out.println(&quot;Hi&quot;);
    }
};

// 람다 방식
Runnable r = () -&gt; System.out.println(&quot;Hi&quot;);</code></pre>
<blockquote>
<p>람다의 기초문법</p>
</blockquote>
<p>1) 매개변수 없을 경우</p>
<pre><code class="language-java">() -&gt; System.out.println(&quot;Hi&quot;)</code></pre>
<p>2) 매개변수 1개 있을 경우</p>
<pre><code class="language-java">x -&gt; x * x</code></pre>
<p>3) 매개변수 2개 있을 경우</p>
<pre><code class="language-java">(a, b) -&gt; a + b</code></pre>
<p>4) 매개변수 다수 있을 경우</p>
<pre><code class="language-java">(x) -&gt; { int y = x+1; return y; }</code></pre>
<hr>
<blockquote>
<p>함수형 인터페이스</p>
</blockquote>
<p>자바는 원래 함수만 따로 전달 될 수 없다. 모든 걸 객체로 다뤄야 해서 함수를 쓰고 싶다면 결국 객체의 메서드로 만들어야 한다.</p>
<p>그래서 인터페이스로 대신하는데, 예전에는 아래와 같이 사용했다.</p>
<pre><code class="language-java">public interface MyFunction {
    void run();
}</code></pre>
<pre><code class="language-java">MyFunction f = new MyFunction() {
    public void run() {
        System.out.println(&quot;실행!&quot;);
    }
};</code></pre>
<p>f.run()을 호출하면 마치 함수처럼 넘긴 것처럼 쓸 수 있는데 이 방식이 너무 길다는 단점이 있다.</p>
<p>이때 등장한 게 람다.</p>
<pre><code class="language-java">// 위 코드를 간단히 줄이면
MyFunction f = () -&gt; System.out.println(&quot;실행!&quot;);</code></pre>
<p>함수형 인터페이스란?
추상 메서드가 하나만 있는 인터페이스</p>
<p>람다식은 함수처럼 생긴 코드지만, 실제로는 자바에서 객체다. 이 객체는 반드시 함수형 인터페이스를 기반으로 만들어져야 한다.
즉, 람다는 클래스 없이도 메서드만 전달할 수 있게 도와주는 문법이며 실제로는 자바가 자동으로 해당 인터페이스의 익명 구현 객체로 변환해주는 것이다.</p>
<p>람다식은 무조건 추상 메서드 하나만 있는 인터페이스를 기반으로 동작한다. 이걸 함수형 인터페이스라고 부르는데 자바에서는 </p>
<p><strong>람다=함수형 인터페이스의 익명 구현체</strong>를 간단히 줄인 문법이라고 이해하면 쉽다.</p>
<pre><code class="language-java">@FunctionalInterface
interface Calculator {
    int calculate(int a, int b);
}
</code></pre>
<h3 id="잠깐-인터페이스는-규칙틀인데-람다랑-뭔-상관임">잠깐! 인터페이스는 규칙(틀)인데 람다랑 뭔 상관임?</h3>
<pre><code class="language-java">interface Animal {
    void sound();
}</code></pre>
<p>위 코드는 sound라는 메서드를 꼭 만들어!라고 규칙만 정해놓은 건데, 실제 동작(내용)은 누가 구현해줘야 한다.</p>
<p>그런데 람다식도 결국 &quot;어떤 동작(기능)&quot;을 전달하는 방식이다.</p>
<pre><code class="language-java">x -&gt; x + 1</code></pre>
<p>이건 숫자 하나 받아서 1 더하는 동작을 말하는 건데, </p>
<p>문제는 동작만 따로 전달하지 못한다. 자바는 함수만 덜렁 넘길 수 없고 무조건 객체로 전달해야 함을 명심하자.</p>
<p>그래서 람다식을 사용할 수 있는 방법으로 함수형 인터페이스를 규칙처럼 만든 거다.</p>
<pre><code class="language-java">@FunctionalInterface
interface Calculator {
    int calculate(int a, int b);
}
// 어떤 계산 로직 하나를 전달해줘.
// 근데 그 계산은 int a, int b 받아서 int 리턴.</code></pre>
<p>위 코드에 의해 람다식이 이렇게 쏙~ 들어간다.</p>
<pre><code class="language-java">Calculator add = (a, b) -&gt; a + b;</code></pre>
<blockquote>
<p>함수형 인터페이스 vs 일반 인터페이스 예시</p>
</blockquote>
<pre><code class="language-java">// 함수형 인터페이스 - 람다식 사용 가능
@FunctionalInterface
interface Printable {
    void print();
}

// 일반 인터페이스 - 추상 메서드 2개 → 람다식 불가능
interface NotFunctional {
    void methodA();
    void methodB();
}</code></pre>
<hr>
<h1 id="streams">Streams</h1>
</br>


<p>Stream이란?
자바 컬렉션(List, Set 등)을 함수형 스타일로 처리할 수 있도록 해주는 API. 필터링, 매핑, 집계 등 연산을 간결하게 수행.</p>
<blockquote>
<p>스트림의 구조</p>
</blockquote>
<pre><code class="language-java">list.stream()         // 스트림 생성
    .filter(...)       // 필터링
    .map(...)          // 값 변환
    .collect(...)      // 결과 수집</code></pre>
<ul>
<li><strong>filter</strong>는 조건을 만족하는 요소만 통과시킴 true인 애만 살아남음</li>
<li><strong>map</strong>은 요소의 값을 변환함 소문자-&gt;대문자</li>
<li><strong>collect</strong>는 스트림 결과를 리스트, 집합 등으로 다시 모아주는 과정</li>
</ul>
<p>이해를 돕기 위한 예제 코드</p>
<pre><code class="language-java">List&lt;String&gt; names = List.of(&quot;Tom&quot;, &quot;Jerry&quot;, &quot;Bob&quot;);

List&lt;String&gt; result = names.stream()
    .filter(name -&gt; name.length() &lt;= 3)
    .map(name -&gt; name.toUpperCase())
    .collect(Collectors.toList());

// 출력: [TOM, BOB]</code></pre>
<p>Stream은 <strong>중간 연산</strong>과 <strong>최종 연산</strong>으로 나뉜다.</p>
<ul>
<li><strong>중간연산</strong>: .filter(), .map() 등 <strong>연결만 하고 실제 실행은 안함.</strong></li>
<li><strong>최종연산</strong>: .collect(), .forEach() 등 이때서야 <strong>실행됨</strong>
즉, 스트림은 <strong>*게으른 평가</strong>를 하므로 collect 같은 최종 연산이 와야 동작이 끝난다.</li>
</ul>
<p><strong>*게으른 평가란?</strong>
스트림은 .filter, .map같은 <strong>중간 연산만으로는 아무것도 실행되지 않고</strong> collect()나 forEach() 같은 <strong>최종 연산이 와야 실행된다.</strong></p>
<pre><code class="language-java">List&lt;String&gt; names = List.of(&quot;Tom&quot;, &quot;Jerry&quot;, &quot;Bob&quot;);

// 실행 안 됨 (아무 결과도 없음)
names.stream()
     .filter(name -&gt; name.length() &lt;= 3)
     .map(String::toUpperCase); // 결과 없음

// 실행됨
names.stream()
     .filter(name -&gt; name.length() &lt;= 3)
     .map(String::toUpperCase)
     .collect(Collectors.toList()); // 실행O

// 또는 실행됨
names.stream()
     .filter(name -&gt; name.length() &lt;= 3)
     .map(String::toUpperCase)
     .forEach(System.out::println); // 실행O, 출력됨</code></pre>
<p><strong>collect()와 forEach()</strong> 는 둘 다 <strong>스트림을 실행시키는 최종 연산</strong>이다.
둘 다 있으면 실행되고, 둘 중 하나만 있어도 실행된다.
차이는 하나는 <strong>데이터 수집</strong>, 하나는 <strong>즉시 실행</strong>이다.
<strong>람다는 스트림 내부에서 이런 연산들을 표현하는 문법 역할</strong>을 한다.</p>
<blockquote>
<p>람다 &amp; 스트림 주의사항</p>
</blockquote>
<ul>
<li>람다는 오직 <strong>함수형 인터페이스</strong>에만 쓸 수 있음</li>
<li>스트림은 <strong>내부 반복</strong>(iteration)이며, <strong>원본 컬렉션은 변경되지 않음</strong></li>
<li>.collect(), .forEach(), .reduce() 등 <strong>중간/최종 연산 구분 필요</strong></li>
</ul>
<blockquote>
<p>그래서 람다랑 스트림, 무슨 관계인데??</p>
</blockquote>
<ul>
<li>Stream 내부에서는 대부분 람다식을 써야 한다.</li>
<li>.filter(), .map(), .forEach()는 <strong>매개변수로 함수형 인터페이스를 받기 때문</strong>임.</li>
<li>람다는 이 함수형 인터페이스를 간단하게 표현하는 방식이니까 → 자연스럽게 <strong>스트림=람다 활용의 대표 사례</strong></li>
</ul>
<pre><code class="language-java">import java.util.List;

public class Main {
    public static void main(String[] args) {
        List&lt;String&gt; names = List.of(&quot;Tom&quot;, &quot;Jerry&quot;, &quot;Bob&quot;);

        // 람다 + 스트림 + forEach (최종 연산)
        names.stream()
            .filter(name -&gt; name.length() &lt;= 3)         // 조건 필터링
            .map(name -&gt; name.toUpperCase())            // 대문자로 변환
            .forEach(name -&gt; System.out.println(name)); // 하나씩 출력
    }
}
// 출력: TOM
// 출력: BOB</code></pre>
<blockquote>
<p>forEach간단 예제 (collect 없이)</p>
</blockquote>
<pre><code class="language-java">List&lt;String&gt; names = List.of(&quot;Tom&quot;, &quot;Jerry&quot;, &quot;Bob&quot;);

names.stream()
    .filter(name -&gt; name.startsWith(&quot;J&quot;))
    .forEach(name -&gt; System.out.println(name)); // 출력: Jerry</code></pre>
<hr>
<blockquote>
<p>질문사항</p>
</blockquote>
<p><strong>Q1. 람다식은 메서드랑 뭐가 다른가?</strong></p>
<p>=&gt; 메서드를 변수처럼 전달 가능. <strong>*익명 구현체의 축약형</strong></p>
<p><strong>*익명 구현체가 뭔데</strong> (별로 안중요한데 궁금해서 알아봄)
이름이 없는 클래스. 인터페이스나 추상 클래스를 구현하면서 이름없이 바로 쓰는 클래스다.</p>
<pre><code class="language-java">// Runnable이라는 인터페이스를 구현하고 싶을 때
Runnable r = new Runnable() {
    @Override
    public void run() {
        System.out.println(&quot;Hi&quot;);
    }
};
r.run();</code></pre>
<p>위 코드에서 Runnable은 인터페이스다.(void run()이라는 메서드 하나만 있음)</p>
<p>그런데 클래스 만들지 않고 바로 new Runnable(){}해서 구현한 건데, 이게 바로 익명 구현 객체(익명 클래스의 인스턴스)</p>
<p>문제는 너무 길고 보기 힘듦=&gt; 그래서 나온 게 람다식</p>
<pre><code class="language-java">// 위 코드를 람다식으로 바꾸면
Runnable r = () -&gt; System.out.println(&quot;Hi&quot;);</code></pre>
<p><strong>Q2. 함수형 인터페이스는 왜 쓰는가?</strong></p>
<p>=&gt; 람다식이 동작하려면 반드시 <strong>1개의 추상메서드가 필요</strong>해서.</p>
<p><strong>Q3. filter()와 map() 차이는?</strong></p>
<p>=&gt; filter는 조건 판별(걸러냄), map은 값 변환(바꿈).</p>
<p><strong>Q4. 스트림 쓸 때 꼭 collect() 해야 해?</strong></p>
<p>=&gt; 최종연산 필요. <strong>결과로 List 등 만들려면 collect() 필요.</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java] enum]]></title>
            <link>https://velog.io/@hansol_choi/Java-enum</link>
            <guid>https://velog.io/@hansol_choi/Java-enum</guid>
            <pubDate>Fri, 18 Jul 2025 09:48:30 GMT</pubDate>
            <description><![CDATA[<p>enum은 클래스다. 클래스정의와 완전 동일하다.</p>
<pre><code class="language-java">public enum OperatorType{
    ADD(&#39;+&#39;), ...
}</code></pre>
<p>내부에 필드, 생성자, 메서드 다 넣을 수 있다? =&gt; enum클래스
enum은 상수들의 모음+부가정보를 담기위한 특수 클래스라고 생각하면 이해하기 쉽다.</p>
<pre><code class="language-java"> public enum OperatorType{
    ADD(&#39;+&#39;),
    SUBTRACT(&#39;-&#39;),
    MULTYPLY(&#39;*&#39;),
    DIVIDE(&#39;/&#39;),
    MOD(&#39;%&#39;);

    private final char symbol;
    // 각 연산자 타입이 가질 기호를 저장할 필드


    OperatorType(char symbol){
    //생성자임. enum상수가 만들어질 때마다 기호를 저장해주는 역할
        this.symbol=symbol;
    }

    public char getSymbol(){
    //원래 getter는 파라미터 안받음: 읽기 전용이기 때문.
    // 외부에서 기호를 읽어올 수 있도록 getter제공
        return symbol;
    }

    public static OperatorType fromChar(char ch){
    // static: 객체 만들지 않고 클래스 이름으로 바로 호출 가능

    // 왜 static으로 했는가?: 외부에서 + 같은 값을 던졌을 때, 
    // 바로 enm상수를 찾아주는 문 입구같은 메서드.

    // OperatorType: enum값 중 하나를 리턴할 거임
    // ch: 입력받은 연산 문제 
    // fromChar=메서드명

        for(OperatorType op: OperatorType.values()){
        //OperatorType.values()는 [ADD, SUBTRACT, MULTYPLY, DIVIDE, MOD]
        // op는 순서대로 ADD, SUBTRACT, MULTYPLY, DIVIDE, MOD 반복

            if(op.symbol==ch) return op;
            // 입력한 문자와 ch 같다면 return 
        }
        throw new ArithmeticException(&quot;지원하지 않는 연산자&quot;);
        //반복문 다 돌았는데도 일치하는 symbol이 없으면 에러 던짐

    }
}</code></pre>
<p> 위 코드 안에 주석으로 설명 달아놓긴 했는데 와닿지가 않아서 구조를 분해해 보며 이해해 보기로 했다.</p>
<blockquote>
<p>public enum OperatorType</p>
</blockquote>
<pre><code class="language-java"> public enum OperatorType {
    ADD, SUBTRACT, MULTIPLY, DIVIDE, MOD
}</code></pre>
<p>+, - 같은 기호값이 없음, 단지 이름만 있고 실제 연산 기호는 if/switch 에서 따로 비교해야 한다.
쓰는 쪽에서 쓸데없이 연산자 기호와 enum을 따로 관리해야 해서 불편하기까지 한다.
그래서 기호에 따라 아래와 같이 switch문을 이용하여 해결해주면 된긴한데..</p>
<pre><code class="language-java">char giho = &#39;+&#39;;
OperatorType opType;

switch (giho) {
    case &#39;+&#39;: opType = OperatorType.ADD; break;
    case &#39;-&#39;: opType = OperatorType.SUBTRACT; break;
    // ... 직접 다 써야 함
}</code></pre>
<p>이렇게 만들게 되면 번거롭고 직관적으로 예쁘지도 않기 때문에 enum을 활용해주면 아주 예쁜 모양이 나온다.</p>
<pre><code class="language-java"> char giho = &#39;+&#39;;
OperatorType opType = OperatorType.fromChar(giho);</code></pre>
<p>아래코드와 같이 완성된 모습을 볼 수 있다.</p>
<pre><code class="language-java"> public static OperatorType fromChar(char ch) {
    for (OperatorType op : OperatorType.values()) {
        if (op.getGiho() == ch) {
            return op;
        }
    }
    throw new ArithmeticException(&quot;지원하지 않는 연산자: &quot; + ch);
}</code></pre>
<p> 즉, 위 코드와 아래 코드와 같은 기능을 한다.</p>
<pre><code class="language-java"> &#39;+&#39; → OperatorType.ADD
&#39;-&#39; → OperatorType.SUBTRACT
... 이런 &quot;변환 작업&quot;이 필요함!</code></pre>
<p>enum 활용하면서  fromChar을 만들어야겠다까지 생각을 못 했다.
그래서 enum사용 시 팁을 적어보자면 아래와 같다</p>
<pre><code class="language-java">num을 만들고,
enum이 외부 입력(char, String, int)을 받아서
자기 자신(enum 상수)을 &quot;찾아주는&quot; 역할이 필요하면
무조건 fromXxx() 메서드 만든다고 기억하자!</code></pre>
<blockquote>
<p>OperatorType.values()</p>
</blockquote>
<pre><code class="language-java">for (OperatorType op : OperatorType.values()) { ... }</code></pre>
<p>enum 클래스가 자동으로 만들어주는 메서드인데, ADD, SUBTRACT,...이런 모든 enum상수들을 배열로 반환해준다.</p>
<pre><code class="language-java"> OperatorType.values() 
// → [ADD, SUBTRACT, MULTIPLY, DIVIDE, MOD]</code></pre>
<blockquote>
<p>private final char symbol;</p>
</blockquote>
<pre><code class="language-java"> private final char symbol;</code></pre>
<p> enum은 불변하기 때문에 final.</p>
<blockquote>
<p>getter는 왜 있고, setter는 왜 없냐?</p>
</blockquote>
<p>enum은 상수이기 때문에 절대 바뀌면 안된다. setter가 필요없다.
getter만 있으면 되는데 외부에서 giho를 읽기 전용으로 제공해준다. </p>
<p>따라서 enum은 setter사용 금지, getter사용 필수.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java]Generics]]></title>
            <link>https://velog.io/@hansol_choi/JavaGenerics</link>
            <guid>https://velog.io/@hansol_choi/JavaGenerics</guid>
            <pubDate>Fri, 18 Jul 2025 09:43:28 GMT</pubDate>
            <description><![CDATA[<h1 id="generics">Generics</h1>
<p>제네릭이란?</p>
<p>List<String>에서 &lt;&gt; 안의 String. 
자료형(타입)을 코드가 실행되기 전에 미리 고정하지 않고 외부에서 나중에 넣게끔 설계하는 문법이다.
쉽게 말하면 자료형을 직접 넣지 말고 &lt;&gt;로 비워두고 나중에 이건 String이야<del>, 이건 Integer야</del> 라고 지정하게 하자.</p>
<blockquote>
<p>왜 제네릭을 쓰는가?</p>
</blockquote>
<p>타입마다 클래스를 일일이 만들면 중복이 심하고 유지보수도 어려움.<br>제네릭은 하나의 클래스만 정의해두고 모든 타입에 재사용 가능하게 해준다.</p>
<p>→ 아래 제네릭 전/후 예시 참고.</p>
<pre><code class="language-java">public class StringBox {
    private String item;

    public void set(String item) {
        this.item = item;
    }

    public String get() {
        return item;
    }
}</code></pre>
<p>위 코드와 같은 클래스가 있다고 가정해보자. 이거는 String 만 담을 수 있는 박스. 그럼 int도 담고 싶을 때 또 만들어야 하나?라는 생각을 할 수 있다.</p>
<pre><code class="language-java">public class IntegerBox {
    private Integer item;
    public void set(Integer item) { this.item = item; }
    public Integer get() { return item; }
}

public class DoubleBox {
    private Double item;
    public void set(Double item) { this.item = item; }
    public Double get() { return item; }
}

public class BooleanBox {
    private Boolean item;
    public void set(Boolean item) { this.item = item; }
    public Boolean get() { return item; }
}</code></pre>
<p>이건 타입마다 클래스를 일일이 새로 만들어야 한다. 중복이 너무 심함.</p>
<p>그래서 이런 중복을 없애고 하나의 클래스로 모든 타입을 처리할 때 제네릭.</p>
<pre><code class="language-java">public class Box&lt;T&gt; {
    private T item;
    public void set(T item) { this.item = item; }
    public T get() { return item; }
}</code></pre>
<p>따라서 제네릭은 재사용 가능한 코드를 만들기 위해 필수</p>
<p>또한 List는 내부 요소의 타입을 모르는 상태로 만들어진다. 그래서 타입 매개변수를 필요로 함.</p>
<p>그래서 List<T> 이런 식으로 선언되어 있고 사용 시 List<String>처럼 구체적인 타입을 만들어줘야 한다.</p>
<p>즉, List, Map, Set같은 컬렉션은 내부적으로 제네릭으로설계되어 있다.</p>
<p>생성된 제네릭은 아래 코드와 같이 사용하면 된다.</p>
<pre><code class="language-java">Box&lt;String&gt; strBox = new Box&lt;&gt;();       // StringBox 역할
Box&lt;Integer&gt; intBox = new Box&lt;&gt;();      // IntegerBox 역할
Box&lt;Double&gt; doubleBox = new Box&lt;&gt;();    // DoubleBox 역할
Box&lt;Boolean&gt; boolBox = new Box&lt;&gt;();     // BooleanBox 역할
</code></pre>
<blockquote>
<p>제네릭의 기초 문법</p>
</blockquote>
<pre><code class="language-java">  class Box&lt;T&gt; {
    private T item;

    public void set(T item) { this.item = item; }
    public T get() { return item; }
}</code></pre>
<p><strong>T는 타입 매개변수(Type parameter)</strong></p>
<p>클래스나 메서드가 어떤 타입을 받을지 미정인 상태로 설계 가능하다.</p>
<pre><code class="language-java">  Box&lt;String&gt; strBox = new Box&lt;&gt;();
strBox.set(&quot;Hello&quot;);
String val = strBox.get(); // 형변환 없이 사용 가능!</code></pre>
<blockquote>
<p>제네릭 클래스 vs 제네릭 메서드</p>
</blockquote>
<p>제네릭 클래스</p>
<pre><code class="language-java">  class Container&lt;T&gt; {
    T data;
}</code></pre>
<p>  제네릭 메서드</p>
<pre><code class="language-java">  public class Util {
    public static &lt;T&gt; void print(T value) {
        System.out.println(value);
    }
}</code></pre>
<p>  <T>가 리턴 타입 앞에 있다. 메서드 자체가 제네릭이라는 뜻이다.</p>
<blockquote>
<p>제네릭 사용 시 주의 사항</p>
</blockquote>
<pre><code class="language-java">    class MyClass&lt;T&gt; { ... }   // 제네릭 클래스
&lt;T&gt; void myMethod(T val)   // 제네릭 메서드</code></pre>
<pre><code class="language-java">  List&lt;int&gt; list = new ArrayList&lt;&gt;();  // 오류 (원시 타입 불가)</code></pre>
<pre><code class="language-java">Box&lt;String&gt; b = new Box&lt;&gt;();
b.set(&quot;hi&quot;); // ok
b.set(123);  // 오류 (String만 허용)</code></pre>
<hr>
<blockquote>
<p>질문사항</p>
</blockquote>
<p>Q1. &lt;&gt; 안엔 꼭 T여야 해?</p>
<p>No. 관례적으로 T를 많이 씀</p>
<p>Q2. &lt;?&gt;는 뭐야?</p>
<p>와일드카드. “모르겠지만 뭔가 타입이 있을 거야” 라는 뜻.
제한을 줄 수 있음 → &lt;? extends Number&gt;, &lt;? super Integer&gt;</p>
<p>Q3. 제네릭은 형변환 안 해도 된다고?</p>
<p>Yes
 제네릭은 컴파일 타임에 타입을 검사해줌 → 실수 예방, 가독성 향상</p>
<pre><code class="language-java">List names = new ArrayList(); // 제네릭 미사용
names.add(&quot;hi&quot;);
String name = (String) names.get(0); // 형변환 필요 x 위험

List&lt;String&gt; names = new ArrayList&lt;&gt;(); // 제네릭 사용
String name = names.get(0); // 형변환 필요 없음 안전</code></pre>
<p>Q4. 제네릭 클래스 vs 제네릭 메서드 구분 맞아?</p>
<p>Yes,</p>
<ul>
<li>제네릭 클래스</li>
</ul>
<pre><code class="language-java">public class Box&lt;T&gt; {
    T item;
}
// → T는 클래스 전체에 적용</code></pre>
<ul>
<li>제네릭 메서드</li>
</ul>
<pre><code class="language-java">public class Util {
    public static &lt;T&gt; void print(T value) {
        System.out.println(value);
    }
}
// → &lt;T&gt;가 메서드 안에서만 사용됨. 클래스 전체는 제네릭 아님.</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[동기처리와 비동기 처리의 차이점에 대해]]></title>
            <link>https://velog.io/@hansol_choi/%EB%8F%99%EA%B8%B0%EC%B2%98%EB%A6%AC%EC%99%80-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%B2%98%EB%A6%AC%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90%EC%97%90-%EB%8C%80%ED%95%B4</link>
            <guid>https://velog.io/@hansol_choi/%EB%8F%99%EA%B8%B0%EC%B2%98%EB%A6%AC%EC%99%80-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%B2%98%EB%A6%AC%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90%EC%97%90-%EB%8C%80%ED%95%B4</guid>
            <pubDate>Thu, 20 Feb 2025 13:12:31 GMT</pubDate>
            <description><![CDATA[<p>동기처리와 비동기처리의 차이는 작업이 처리되는 방식에서 차이가 있다.</p>
<p>동기처리는 작업을 순차적으로 처리하는 방식으로 하나의 작업이 끝난 후에야 다음 작업이 실행된다. 단점으로는 시간이 오래 걸리는 작업이 있으면 전체 프로그램의 실행이 지연될 수 있다.</p>
<p>비동기 처리는 작업을 병렬로 처리하며 하나의 작업을 기다리지 않고 다른 작업을 동시에 실행한다. 예를들어, 네트워크요청을 보낸 후 결과를 기다리는 동안 다른 일을 할 수 있다. 비동기처리는 주로 I/O작업에서 사용되며 프로그램의 효율성을 높여준다.</p>
<p>즉 동기처리는 순차적실행, 비동기처리는 동시에 여러 작업을 실행할 수 있다는 차이가 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스크립트의 객체지향 프로그래밍에 대해]]></title>
            <link>https://velog.io/@hansol_choi/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%EC%97%90-%EB%8C%80%ED%95%B4</link>
            <guid>https://velog.io/@hansol_choi/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%EC%97%90-%EB%8C%80%ED%95%B4</guid>
            <pubDate>Thu, 20 Feb 2025 13:09:45 GMT</pubDate>
            <description><![CDATA[<p>자바스크립트의 객체지향 프로그래밍은 데이터를 객체로 묶고 객체가 가지는 속성과 기능을 정의하는 방식이다. 자바스크립트는 프로토타입 기반의 객체지향언어로 클래스 없이도 객체지향 개념을 사용할 수 있다. 하지만 ES6부터 클래스 문법이 도입되어 객체를 생성하는 템플릿인 클래스를 사용해 객체지향 프로그래밍을 더 직관적으로 할 수 있게되었다.
객체지향의 핵심 개념은 상속, 캡슐화, 다형성이다.</p>
<p>상속은 부모클래스의 속성과 메서드를 자식 클래스가 물려받는 것으로 코드 재사용을 가능하게 한다. </p>
<p>캡슐화는 객체의 속성을 숨기고 외부에서 안전하게 다룰 수 있게 하는 개념이다.</p>
<p>다형성은 같은 이름의 메서드가 다른 동작을 하게 만드는 원리로 여러 클래스에서 같은 메서드를 다르게 구현할 수 있게 해준다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스크립트의 데이터 타입과 Tyscript의 데이터타입에 대해]]></title>
            <link>https://velog.io/@hansol_choi/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%83%80%EC%9E%85%EA%B3%BC-Tyscript%EC%9D%98-%EB%8D%B0%EC%9D%B4%ED%84%B0%ED%83%80%EC%9E%85%EC%97%90-%EB%8C%80%ED%95%B4</link>
            <guid>https://velog.io/@hansol_choi/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%83%80%EC%9E%85%EA%B3%BC-Tyscript%EC%9D%98-%EB%8D%B0%EC%9D%B4%ED%84%B0%ED%83%80%EC%9E%85%EC%97%90-%EB%8C%80%ED%95%B4</guid>
            <pubDate>Thu, 20 Feb 2025 13:02:19 GMT</pubDate>
            <description><![CDATA[<p>가장 큰 차이점은 타입시스템이다.
자바스크립트는 동적 타이핑언어로 변수의 타입이 실행중에 자동으로 결정된다. 즉, 타입을 명시적으로 지정할 수 없고 코드 실행시에만 오류를 확인 할 수 있다. 이로인해 런타임에 타입관련 오류가 발생할 수 있고 대규모 프로젝트에서 버그를 미리 잡기 어려운 문제가 있다.</p>
<p>반면 타입스크립트는 정적 타이핑을 지원하는 언어다. 즉 변수에 타입을 명시적으로 지정할 수 있으며 컴파일 타임에 타입오류를 미리 확인 할 수 있다. 타입스크립트는 자바스크립트의 기본 타입을 그대로 사용하면서도 any, unknown, never, tuple, enum과 같은 추가적인 타입을 제공하며 코드의 안정성을 높이고 유지보수를 용이하게 한다.
예를들어 any는 어떤 타입이든 허용되지만 타입스크립트는 이를 사용하는 대신 unknown을 사용해 타입체크를 강제하여 더 안전한 코드를 작성할 수 있다.</p>
<p>따라서 자바스크립트는 실행중에 오류가 발생할 수 있는 동적타입언어인 반면, 타입스크립트는 개발과정에서 타입을 명시적으로 정의하고 오류를 미리 예방할 수 있는 정적타입언어다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[기술분석: 프론트엔드 에러 모니터링, 로그 데이터 수집의 필요성]]></title>
            <link>https://velog.io/@hansol_choi/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%97%90%EB%9F%AC-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81-%EB%A1%9C%EA%B7%B8-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%88%98%EC%A7%91%EC%9D%98-%ED%95%84%EC%9A%94%EC%84%B1</link>
            <guid>https://velog.io/@hansol_choi/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%97%90%EB%9F%AC-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81-%EB%A1%9C%EA%B7%B8-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%88%98%EC%A7%91%EC%9D%98-%ED%95%84%EC%9A%94%EC%84%B1</guid>
            <pubDate>Fri, 14 Feb 2025 15:09:04 GMT</pubDate>
            <description><![CDATA[<ul>
<li><input disabled="" type="checkbox"> 기술분석 : 프론트엔드 에러 모니터링, 로그 데이터 수집의 필요성 <ul>
<li><input disabled="" type="checkbox"> Sentry 기반 애플리케이션 에러 모니터링</li>
<li><input disabled="" type="checkbox"> vercel을 이용한 배포</li>
</ul>
</li>
</ul>
<p>프론트엔드 애플리케이션에서 에러는 사용자의 경험을 크게 저하시킬 수 있습니다. 에러를 빠르게 파악하고 해결하는 것이 중요하며, 이를 위한 효율적인 도구로 Sentry를 활용할 수 있습니다. 본 글에서는 Sentry를 기반으로 애플리케이션의 에러 모니터링을 어떻게 설정하고 활용할 수 있는지, 그리고 배포 과정에서 Vercel을 이용한 배포 방법에 대해 다룹니다.</p>
<p>Sentry 기반 애플리케이션 에러 모니터링
Sentry는 애플리케이션의 에러를 실시간으로 추적할 수 있는 강력한 도구입니다. React를 포함한 다양한 프레임워크에서 발생할 수 있는 에러를 자동으로 추적하고, 자세한 에러 로그를 제공합니다. 이를 통해 개발자는 에러 발생 원인을 빠르게 파악하고, 신속하게 문제를 해결할 수 있습니다.</p>
<h4 id="1-에러-예시-1-target-container-is-not-a-dom-element">1. 에러 예시 1: Target container is not a DOM element</h4>
<p>문제:
React 애플리케이션에서 ReactDOM.createRoot를 호출할 때, 잘못된 DOM 요소를 참조하면 Target container is not a DOM element라는 에러가 발생할 수 있습니다. 이는 일반적으로 ReactDOM.createRoot에서 지정한 컨테이너가 실제로 DOM에 존재하지 않거나 잘못된 참조일 때 발생합니다.</p>
<p>Sentry에서의 에러 로그:
에러 메시지: Target container is not a DOM element
문제 발생 위치: /src/main.tsx (line 40)
Stack Trace: 에러가 발생한 파일과 라인 정보를 제공하여, 빠르게 문제를 추적할 수 있습니다.</p>
<p>해결 방법:
Sentry가 제공하는 에러 상세 정보를 통해 createRoot 메소드가 참조하는 DOM 요소가 올바르게 설정되었는지 확인할 수 있습니다. 잘못된 DOM 요소 참조를 수정하면 이 에러를 해결할 수 있습니다.</p>
<h4 id="2-에러-예시-2-typeerror---object-object-object-has-no-method-updatefrom">2. 에러 예시 2: TypeError - Object [object Object] has no method &#39;updateFrom&#39;</h4>
<p>문제:
이 에러는 객체에 updateFrom 메소드가 정의되어 있지 않거나 잘못된 객체에 해당 메소드가 호출되었을 때 발생합니다. 특히, raven.js와 관련된 코드에서 이런 문제가 발생하는 경우가 많습니다.</p>
<p>Sentry에서의 에러 로그:
에러 메시지: TypeError - Object [object Object] has no method &#39;updateFrom&#39;
문제 발생 위치: views.js (line 389)</p>
<p>해결 방법:
에러 발생 위치와 객체를 추적하여, 해당 객체가 올바르게 메소드를 호출할 수 있도록 수정해야 합니다. Sentry의 디버깅 정보를 통해 코드 흐름을 쉽게 파악할 수 있습니다.</p>
<h4 id="sentry를-통한-에러-추적의-장점">Sentry를 통한 에러 추적의 장점</h4>
<p>Sentry는 다음과 같은 기능을 제공합니다:</p>
<p>자동 에러 추적: 애플리케이션에서 발생한 에러를 자동으로 수집하여 실시간으로 추적할 수 있습니다.
Stack Trace: 에러가 발생한 정확한 파일과 라인 정보를 제공합니다. 이를 통해 개발자는 에러 발생 지점을 빠르게 찾을 수 있습니다.
브라우저 및 환경 정보: 에러 발생 시 사용자의 브라우저, 운영 체제 정보 등을 제공하여, 특정 환경에서만 발생하는 문제를 파악할 수 있습니다.
세션 리플레이: 사용자가 에러를 발생시키는 시점을 녹화하여 재생할 수 있어, 문제를 재현하고 디버깅하는 데 도움이 됩니다.</p>
<h4 id="vercel을-이용한-배포">Vercel을 이용한 배포</h4>
<p>Vercel은 React 애플리케이션을 빠르고 쉽게 배포할 수 있는 플랫폼입니다. Vercel은 자동으로 빌드 및 배포를 처리해 주므로, 개발자는 배포 과정을 신경 쓰지 않고 개발에 집중할 수 있습니다.</p>
<h4 id="vercel-배포-방법">Vercel 배포 방법:</h4>
<p>Vercel에 가입하고 로그인: Vercel 공식 사이트에서 계정을 생성하고 로그인합니다.
GitHub 연동: Vercel은 GitHub과 통합되어 있어, 리포지토리를 선택하여 배포할 수 있습니다.
배포: 배포할 프로젝트를 선택하고, Vercel이 자동으로 빌드 및 배포를 진행합니다. 배포가 완료되면, 자동으로 배포된 사이트의 URL을 제공합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[유닛 테스트 기술분석 : 왜 필요한지, 어떤 라이브러리가 있는지 ]]></title>
            <link>https://velog.io/@hansol_choi/%EC%9C%A0%EB%8B%9B-%ED%85%8C%EC%8A%A4%ED%8A%B8%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80</link>
            <guid>https://velog.io/@hansol_choi/%EC%9C%A0%EB%8B%9B-%ED%85%8C%EC%8A%A4%ED%8A%B8%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80</guid>
            <pubDate>Fri, 14 Feb 2025 15:08:27 GMT</pubDate>
            <description><![CDATA[<ul>
<li><input disabled="" type="checkbox"> 유닛 테스트 기술분석 : 왜 필요한지, 어떤 라이브러리가 있는지 관련 blog 작성<ul>
<li><input disabled="" type="checkbox"> Jest 기반 유닛 테스트 코드 작성 및 테스트</li>
<li><input disabled="" type="checkbox"> Day 2에서 작성했던 컴포넌트 중, 유닛 테스트 2회 이상 수행하기</li>
</ul>
</li>
</ul>
<h4 id="유닛-테스트란">유닛 테스트란?</h4>
<p>유닛 테스트는 <strong>작은 코드 단위(함수나 컴포넌트)</strong>가 제대로 작동하는지 확인하는 작업입니다.
예를 들어, 웹사이트에서 &quot;버튼을 클릭하면 텍스트가 바뀌는지&quot; 이런 동작들이 정확한지 체크할 수 있습니다. 이렇게 테스트를 해두면, 나중에 코드를 수정할 때 &quot;이게 정말 잘 작동할까?&quot;라는 걱정을 덜 수 있습니다.</p>
<h4 id="왜-유닛-테스트가-필요한가">왜 유닛 테스트가 필요한가?</h4>
<p>유닛 테스트를 하는 이유는 버그(문제)를 미리 찾아내고 예방하기 위해서입니다.
예를 들어, 나중에 코드를 수정했을 때 그 수정으로 인해 문제가 생겼다면, 유닛 테스트가 이미 이를 알려줄 수 있게됩니다. 그래서 안전하게 코드를 수정하거나 새 기능을 추가할 수 있게 돕습니다.</p>
<h4 id="유닛-테스트-라이브러리">유닛 테스트 라이브러리</h4>
<p>Jest: Jest는 가장 많이 사용되는 유닛 테스트 도구 중 하나입니다.
설정이 간단하고, React나 JavaScript에서 사용됩니다. 또한 빠르게 테스트를 실행하고, 실패한 테스트는 쉽게 찾을 수 있게 도와줍니다.</p>
<p>Mocha: Node.js 환경에서 많이 사용됩니다.
비동기 테스트에 강점이 있어서 서버 관련 테스트에 유용합니다.</p>
<p>Chai: Chai는 Mocha와 함께 사용되는 라이브러리로, 테스트 결과를 좀 더 직관적으로 표현할 수 있게 도와줍니다. 어떤 값이 맞는지 확인하는 assertion을 쉽게 작성할 수 있습니다.</p>
<h4 id="유닛-테스트-두-번째-테스트-작성하기">유닛 테스트: 두 번째 테스트 작성하기</h4>
<pre><code>import { render, screen, fireEvent } from &#39;@testing-library/react&#39;;
import MyComponent from &#39;./MyComponent&#39;;

test(&#39;버튼을 클릭하면 텍스트가 바뀐다&#39;, () =&gt; {
  render(&lt;MyComponent /&gt;);
  const button = screen.getByRole(&#39;button&#39;);
  fireEvent.click(button);
  const changedText = screen.getByText(&#39;텍스트 변경됨&#39;);
  expect(changedText).toBeInTheDocument();
});
</code></pre><p>이 코드는 &quot;버튼을 클릭했을 때 텍스트가 &#39;텍스트 변경됨&#39;으로 바뀌는지&quot;를 테스트해요. 테스트가 성공하면, 이 버튼이 제대로 동작합니다.</p>
<p>지금까지 Jest로 유닛 테스트를 작성하고 있었고, Day 2에서 작성했던 컴포넌트에 대해서도 테스트를 진행해야 한다고 했습니다. 방금 전에 작성한 코드처럼 버튼을 클릭하면 텍스트가 바뀌는지 같은 간단한 테스트를 할 수 있습니다.</p>
<p><strong>1. 첫 번째 테스트:  SignIn 테스트</strong></p>
<pre><code>import { render, screen, fireEvent } from &#39;@testing-library/react&#39;;
import SignIn from &#39;../SignIn&#39;;  

test(&#39;로그인 버튼을 클릭하면 로그인 폼이 제출된다&#39;, () =&gt; {
  render(&lt;SignIn /&gt;);  

  const usernameInput = screen.getByLabelText(/사용자 이름/i);  
  // 사용자 이름 입력 필드 찾기
  const passwordInput = screen.getByLabelText(/비밀번호/i);  
  // 비밀번호 입력 필드 찾기
  const submitButton = screen.getByRole(&#39;button&#39;, { name: /로그인/i });  
  // 로그인 버튼 찾기

  // 입력 필드에 값 넣기
  fireEvent.change(usernameInput, { target: { value: &#39;testuser&#39; } });
  fireEvent.change(passwordInput, { target: { value: &#39;password123&#39; } });

  // 로그인 버튼 클릭
  fireEvent.click(submitButton);

  // 로그인 성공 메시지 확인 (혹은 화면 변화 확인)
  const successMessage = screen.getByText(/로그인 성공/i);
  expect(successMessage).toBeInTheDocument();
});
</code></pre><p>SignIn.test.tsx에서 우리는 로그인 화면을 테스트했던 것 같아요. 사용자가 로그인 폼을 올바르게 제출할 수 있는지, 그리고 로그인에 필요한 필드들이 제대로 동작하는지 확인하는 내용입니다.
이 코드는 SignIn 컴포넌트에서 사용자 이름과 비밀번호를 입력하고 로그인 버튼을 클릭했을 때, 로그인 성공 메시지가 나타나는지 테스트하는 내용입니다. fireEvent.change()로 입력 필드에 값을 넣고, fireEvent.click()으로 버튼을 클릭하는 동작을 처리하고 있습니다.</p>
<p><strong>2. 두 번째 테스트: Header 테스트</strong></p>
<pre><code>import { render, screen, fireEvent } from &#39;@testing-library/react&#39;;
import Header from &#39;../Header&#39;;  

test(&#39;헤더에서 로그인/회원가입 버튼을 클릭하면 해당 페이지로 이동한다&#39;, () =&gt; {
  render(&lt;Header /&gt;);  

  const loginButton = screen.getByRole(&#39;button&#39;, { name: /로그인/i });  
  // 로그인 버튼 찾기
  const signUpButton = screen.getByRole(&#39;button&#39;, { name: /회원가입/i });  
  // 회원가입 버튼 찾기

  // 로그인 버튼 클릭
  fireEvent.click(loginButton);
  // 로그인 페이지로 이동했다고 가정하고, 해당 페이지의 요소가 있는지 확인
  expect(screen.getByText(/로그인 페이지/i)).toBeInTheDocument();

  // 회원가입 버튼 클릭
  fireEvent.click(signUpButton);
  // 회원가입 페이지로 이동했다고 가정하고, 해당 페이지의 요소가 있는지 확인
  expect(screen.getByText(/회원가입 페이지/i)).toBeInTheDocument();
});
</code></pre><p>이 테스트는 로그인과 회원가입 버튼이 클릭될 때 해당 페이지로 잘 이동하는지를 확인하는 테스트입니다. fireEvent.click()으로 버튼 클릭을 시뮬레이션하고, 페이지 이동을 확인하는 방식입니다.</p>
<p>이렇게 <strong>SignIn</strong>과 Header 컴포넌트에 대한 유닛 테스트를 작성함으로써, 중요한 동작들이 잘 동작하는지 자동으로 확인할 수 있습니다. Jest를 사용하면 사용자와의 상호작용을 테스트하고, 컴포넌트가 올바르게 작동하는지 쉽게 검증할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JWT 기술분석 : access, refresh token의 개념과 관리전략 ]]></title>
            <link>https://velog.io/@hansol_choi/JWT-%EA%B8%B0%EC%88%A0%EB%B6%84%EC%84%9D</link>
            <guid>https://velog.io/@hansol_choi/JWT-%EA%B8%B0%EC%88%A0%EB%B6%84%EC%84%9D</guid>
            <pubDate>Thu, 13 Feb 2025 12:55:45 GMT</pubDate>
            <description><![CDATA[<p>JWT(JSON Web Token)란?
JWT는 사용자가 로그인하면 발급되는 디지털토큰으로 사용자의 인증정보를 담고있다. 주로 웹 서비스에서 로그인상태를 유지하는데 사용되며 Access Token과 Refresh Token 두가지 종류가 있다.</p>
<p>Access Token과 Refresh Token의 개념</p>
<ol>
<li>Access Token
Access Token은 사용자가 로그인했음을 증명하는 출입증과 같다. 이를 가지고 있어야 서버에서 데이터를 요청할 수 있다.</li>
</ol>
<p>특징</p>
<ul>
<li>로그인하면 발급됨</li>
<li>짧은 유효기간(보통 15분~1시간)</li>
<li>API요청 시 Authorization: Bearer <access_token>에 포함해 사용</li>
<li>탈취되면 보안 위험이 있음</li>
</ul>
<ol start="2">
<li>Refresh Token
Refrech Token은 Access Token이 만료되었을 때, 새로운 Access Token을 발급받기 위한 갱신 키 역할을 한다.</li>
</ol>
<p>특징</p>
<ul>
<li>로그인하면 Access Token과 함께 발급됨</li>
<li>유효기간이 길다(7일~30일)</li>
<li>만료된 Access Token을 갱신하는 역할</li>
<li>탈취 시 보안 위험이 크므로 안전하게 보관해야 함</li>
</ul>
<p>JWT가 동작하는 방식</p>
<ol>
<li>사용자가 로그인하면 서버가 Access Token과 Refresh Token 발급함</li>
<li>클라이언트(웹/앱)가 Access Token을 API요청에 포함해 서버에 전송함</li>
<li>서버가 Access Token을 검증하고 요청을 처리함</li>
<li>Access Token이 만료되면 클라이언트가 Refrech Token을 사용해 새로운 Access Token을 요청함</li>
<li>서버가 Refresh Token을 확인하고 새로운 Access Token을 발급함</li>
<li>Refresh Token까지 만료되면 사용자는 다시 로그인해야 함</li>
</ol>
<p>안전한 JWT관리법</p>
<ol>
<li>Access Token 저장방식</li>
</ol>
<ul>
<li>메모리저장(권장)-&gt; XSS 공격을 방어할 수 있음</li>
<li>HTTP-Only쿠키 저장-&gt; 브라우저에서 접근할 수 없도록 보호</li>
<li>LocalStorage(비추천)-&gt;XSS공격에 취약함</li>
</ul>
<ol start="2">
<li>Refresh Token저장방식</li>
</ol>
<ul>
<li>Secure HTTP-Only쿠키(권장)-&gt; 쿠키에 저장하고 httpOnly설정</li>
<li>데이터베이스 저장-&gt; 서버에서 관리하고 요청할 때 검증</li>
<li>Redis사용-&gt; 빠른 접근과 관리가 가능</li>
</ul>
<ol start="3">
<li>보안강화 전략</li>
</ol>
<ul>
<li>Refresh Token을 한 번만 사용하도록 설정(재사용 방지)</li>
<li>로그아웃하면 Refresh Token을 즉시 폐기</li>
<li>JWT 서명을 검증해 변조 여부 확인</li>
<li>IP 및 User-Agent변경 감지 후 추가 인증요구</li>
</ul>
<p>JWT 사용 시 주의할 점</p>
<ul>
<li>Access Token의 유효기간을 짧게 설정해 탈취위험을 줄이기</li>
<li>Refresh Token은 서버에서 안전하게 보관하기</li>
<li>HTTPS를 사용해 네트워크에서 토큰이 노출되지 않도록 하기</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>