블로그

leeebug

워밍업 클럽 스터디 3기 FS - 2주차 발자국

인프런 워밍업 클럽 스터디에 참여하고 벌써 2주 차도 마무리에 접어들고 있다. 4주간의 스터디이기 때문에 생각보다 일정이 타이트하여 시간관리가 무엇보다도 중요한 시기라고 생각한다.이번 주에는 파일 업로드 기능을 구현해야하기에 1주 차 과제를 급하게 마무리하고 지난주 토요일부터 서둘어서 미리 학습을 시작했다.깃 레포지토리의 경우에는 지난주에 사용했던 템플릿을 거의 수정없이 그대로 사용해서 개발환경 구축은 크게 어렵지 않았다.이번 주 강의에서는 Supabase Storage를 사용했는데 API가 잘 준비되어있어서 사용 방법을 익히는데도 크게 어렵지는 않았다. 다만 아래에서도 언급하겠지만 Supabase Storage는 AWS S3 기반으로 구현되어 강력한 네이밍 규칙이 적용되어 개인적으로는 Supabase Storage와 Supabase DB를 함께 사용했다. 📝 2주차 학습Supabase Storage클라우드 기반 객체 저장소로, AWS S3와 유사한 방식으로 파일을 저장하고 관리하는 서비스파일 및 이미지 업로드 및 관리 기능 제공PostgreSQL과 연동 가능권한 관리(RLS) 및 퍼블릭/프라이빗 파일 설정 가능Supabase SDK 또는 Restful API로 사용 가능  ✔ 파일명 규칙 (Supabase & AWS 공통) ASCII 문자, 숫자, 일부 특수 문자 허용 (- _ . /) 파일명을 /로 구분하여 폴더처럼 사용 가능 (folder/image.png) 공백 포함 가능하지만, URL Encoding이 필요할 수 있음 파일명에 한글, 이모지, 특수문자가 포함될 경우 정상적으로 업로드되지 않을 가능성이 있음 → URL-safe 변환 권장 React DropzoneReact에서 간편하게 파일 Drag & Drop 기능을 구현할 수 있는 라이브러리HTML5 File API를 활용하여 파일 업로드를 쉽게 구현할 수 있는 기능을 제공파일 타입, 크기, 개수 등 다양한 제약 조건 설정 가능비동기로 파일을 처리할 수 있는 onDrop 이벤트 제공 📋 2주차 미션💬 GitHub 저장소🚀 데모 영상 보러가기미션 해결 과정 요약2주 차 미션은 Next.js, React Query, TailwindCSS를 사용하여 이미지 업로드 앱을 구현하기였다. 필수 구현 기능으로는 이미지 업로드 기능(클릭 업로드 방식과 Drag & Drop 방식, 다중 업로드)과 이미지 삭제와 이미지 검색 기능 구현하기였다. 추가 기능은 파일의 마지막 수정 시간을 화면에 출력하는 UI 구현하기였다. 여기에 과제의 완성도를 높이기 위해서 개인적인 챌린지로 파일명에 한글 또는 특수문자 포함된 파일 업로드 기능, 1MB 미만으로 이미지를 압축하는 기능, 다운로드 기능을 추가로 구현하였다. 과제 추가 구현 기능✅ 마지막 수정 시간 표시const { error: insertError } = await supabase.from(DB_TABLE_NAME).insert({ name: file.name, originalName: originalFileName, imageId: uploadedFile.id, imageUrl: publicUrl, createdAt: new Date(file.lastModified).toISOString(), })생성: DB에 파일 데이터 업로드 시 createdAt에 file.lastModified를 ISOString 형식으로 저장 if (dbData) { const { error: updateError } = await supabase .from(DB_TABLE_NAME) .update({ name: file.name, originalName: originalFileName, imageUrl: publicUrl, updatedAt: new Date().toISOString(), }) .eq('imageId', uploadedFile.id)수정: DB에 해당 ID가 존재할 경우 updatedAt(string | null)에 현재 시간을 ISOString 형식으로 저장// DropImageManager 컴포넌트에서 생성 시간, 수정 시간을 포멧팅하여 DropImage 컴포넌트에 프롭스로 전달 const localCreatedAt = getLocalTime(image.createdAt) const localUpdatedAt = image.updatedAt ? getLocalTime(image.updatedAt) : null <!-- JSX 정렬이 잘 안되서 렌더링 형태만 봐주세요! --> <div className="w-5/6 truncate"> <span className={`text-[0.7rem] font-semibold ${localUpdatedAt ? 'text-mint-800' : 'text-gray-500'}`} > {localUpdatedAt ? localUpdatedAt : localCreatedAt} </span> {localUpdatedAt && ( <span className="text-[0.7rem] font-semibold text-mint-800"> (수정)</span> )} </div>출력: updatedAt이 존재할 경우 updatedAt과 (수정) 을 함께 출력, updatedAt: null이라면 createdAt를 출력 개인 챌린지 기능✅ 파일명 자동 변환 후 이미지 업로드하는 기능을 구현 (UX 개선)파일명 검증: 정규식을 활용하여 한글 및 특수 문자 포함 여부를 확인자동 변환: 검증 후 8자리 랜덤 문자열로 안전한 파일명 생성업로드 처리: 변환된 파일명으로 File 객체 생성 후 formData.append로 원본 파일명 함께 전송서버 액션: Supabase Storage에 저장 후, 완료 시 DB에 메타데이터 저장하여 연동결론: 파일명 변환을 자동화하여 업로드 오류를 방지하고, 원본 파일명도 유지하여 검색 및 관리 UX 개선✅ 파일 용량이 1MB 초과 시 자동 압축 후 업로드하는 기능을 구현 (UX 개선)browser-image-compression 라이브러리를 사용하여 파일의 용량 검증 후 1MB 초과 시 이미지 압축 후 업로드결론: 이미지 최적화로 업로드 속도 향상, 스토리지 비용 절감 효과✅ Blob URL을 활용한 다운로드 기능 추가 (UX 개선)Blob URL 생성: 업로드된 이미지를 fetch()로 가져와 Blob으로 변환다운로드 기능 구현: window.URL.createObjectURL(blob)으로 브라우저에서 직접 다운로드 가능하도록 처리결론: Blob URL 다운로드 방식을 적용하여 최적화된 이미지를 빠르게 다운로드 받을 수 있도록 개선🚧 기능 구현 시 어려웠던 부분Supabase Storage에 전달하는 File 객체 커스텀 불가원본 파일명을 추가하려 했으나, File 객체 자체를 수정하는 것이 제한적이다.파일 객체를 복사하여 원본 파일명을 추가하는 방법 시도전개 연산자를 사용하여 객체 복사 후 원본 파일명을 추가하려 시도하였으나 file 객체는 일반적인 방법으로는 복사할 수 없는 특별한 객체이다.ExtendedFile 확장 클래스로 인스턴스를 생성했으나 서버에 전달되지 않는 문제 발생확장된 ExtendedFile 객체를 formData에 담아 서버로 전송했지만, 서버에 정상적으로 전달되지 않았다.최종 해결 방법formData.append("file", file) formData.append("originalFileName, file.name)file 객체와 원본 파일명을 함께 서버로 전송 후 가공하여 Supabase Storage의 파일명에는 안전한 파일명만 저장하고 DB에 스토리지Id, 원본 파일명, 안전한 파일명, 이미지URL 등 정보를 저장했다. 🧾 ERD 다이어그램👀 2주차 회고아직 갈 길이 멀지만, 리팩토링을 통해 Next.js의 장점을 살릴 수 있는 구조로 점점 개선되어가는 과정을 경험하면서 이번 주 역시 알차게 보냈다고 생각한다.이번 주는 특히 MVP 패턴과 비슷한 형태로 컴포넌트 구조를 잡는 것에 익숙해지는 것을 개인적인 목표로 삼았다. 처음부터 MVP 패턴을 염두해 두고 설계한 것은 아니었지만, 진행하다보니 자연스럽게 MVP와 유사한 패턴으로 정리되어 가는 것을 느꼈다.화면 렌더링 시 상호작용이 필요하지 않은 정적인 요소들까지 클라이언트 컴포넌트로 관리하면 불필요한 하이드레이션 부담이 증가할 수 있다는 점을 다시 한번 체감했다.클라이언트 컴포넌트 내에서도 역할을 나눠 서비스 레이어나 상태 관리만 담당하는 매니져 컴포넌트와 프롭스로 상태를 전달받아 단순히 화면을 렌더링을 담당하는 UI 컴포넌트로 분리하는 연습을 진행했다.이러한 구조로 개선하면서 클라이언트 컴포넌트의 부담을 줄이고, 유지보수성을 높이는 방향으로 점차 최적화되고 있다는 점이 느껴졌다. 아직 개선해야 할 부분이 많지만 점진적으로 개선하여 더 나은 아키텍쳐를 만들어가는 과정이 의미 있었다고 생각한다.

풀스택워밍업클럽3기발자국회고과제미션

10

우빈님의 세심한 코드 리뷰 - 인프런 워밍업 클럽 3기 백엔드 코드 ✨

인프런 워밍업 클럽 3기 백엔드 코드 발자국 1주차 🙊 시작이 반이다... 벌써 중간점검 ?인프런 워밍업 클럽이 시작한지 2주가 지났다.. 앞으로 남은 발자국이 2개 뿐이다.. 👣이번주에는 중간점검으로 온라인 라이브가 진행 되었다. 온라인 라이브에 대한 내용은 다음과 같다.미션 Day 4 에 대한 공통 피드백Q&A에 대한 답변미션 Day 7 코드리뷰 진행각 세션은 놀라울 정도의 세심한 우빈님의 피드백 덕분에 많은 인사이트를 얻게 되어 좋은 시간이었다. 나도 첫번째로 코드리뷰를 신청하고 라이브 마지막에 코드리뷰를 받았는데 너무 좋은 경험이었다. 💪(커피를 좋아하시기로 유명한 우빈님께 Q&A 세션 도중 저가 커피 브랜드 중 어디 브랜드가 제일 맛있냐는 질문이 나왔는데.. 드셔 보신 적이 없다고 답변해 주신 부분이 인상적이었다.. ㅋㅋ 😁)✨ 우빈님의 세심한 코드 리뷰위에서 이야기했듯이 코드리뷰를 첫번째로 신청해서 우빈님께 온라인 라이브 시간에 코드리뷰를 받았다.해당 미션은 "스터디 카페" 프로그램을 리팩토링하는 미션이였는데.. 작년 4분기에 강의를 수강했을 당시에 3번이나 진행하였다.첫 번째 리팩토링, 강의를 듣기 전에 리팩토링두 번째 리팩토링, 강의를 수강하며 리팩토링세 번째 리팩토링, 강의를 수강 후 정리하며 다시 리팩토링이번이 네 번째 리팩토링이였는데 할 때마다 왜 새로운 것인지.. 🥲그래도 손이 기억이라도 한 듯 나름 순조롭게(?) 미션을 진행하게 되었고, 추가적으로 리팩토링을 진행하였고 해당 부분을 리뷰를 받고 싶어 신청하게 되었다.(우빈님께서 4번이라는 부분에 놀라셨는지(?).. 디스코드 스레드에 댓글을 남겨주셨다! 🤣)다시 돌아와서.. 코드리뷰 받은 내용은 아래와 같다.1⃣ 중요 도메인 StudyCafePassType의 구조화 🔗 Github PR 링크StudyCafePassType 구조화 리팩토링♻ 리팩토링 코드public enum StudyCafePassType implements PassTypeSelectable, PassTypeFormatter { // 📝 인터페이스 구조화 HOURLY("시간 단위 이용권") { // 📝 사용자 입력에 대한 구조화 @Override public boolean selected(String userInput) { return "1".equals(userInput); } // 📝 사용자 출력 포맷에 대한 구조화 @Override public String format(StudyCafePass pass) { return String.format("%s시간권 - %d원", pass.getDuration(), pass.getPrice()); } } }✏ 우빈님 리뷰Q. 클래스 내부에서 사용자 입력값 및 출력값에 사용하는 인터페이스를 구현함으로써 오버 엔지니어링이 된 것 같은 느낌이 드네요.. 🤦‍♂️ A. 저도 그렇게 생각해요.. ㅋㅋㅋㅋ 오버 엔지니어링이기보다 PassType은 중요한 도메인 모델인데, Input에서만 의미를 가지는 사용자 선택지가 침투하고 있다. 사용자 선택 방법이 "a", "b", "c"로 바뀐다면? 단순히 입력 방식을 바꿨을 뿐인데 무료 도메인 모델이 수정되어야 하는 엄청난 사태가 발생한다. 항상 구조화를 하는 것이 정답은 아니다. Output format도 마찬가지이다. 책임이 우선이다. 적절한 책임의 분배가 객체의 결합도를 낮추고 응집도를 높이는 것이다. 🤔 돌아보기단순히, OCP를 적용하기 위해 접근해서 리팩토링 했었는데..적절한 객체 책임 분리를 하지 못했으며, 중요한 도메인 모델을 수정하는 엄청 큰 사이드 이펙트가 일어날 수 있다는 점을 간과 했다는 것이다.다음부터는 구조화를 남발하지 않고 책임에 집중해서 리팩토링 해야겠다..2⃣ 이용권을 읽는 부분과 읽은 부분의 개념을 추출하여 객체 분리 🔗 Github PR 링크ReadLockerPasses 객체 분리♻ 리팩토링 코드public class ReadLockerPasses { // 📝 LockerPasses를 해석하는 객체 분리 private final List<StudyCafeLockerPass> passes; private ReadLockerPasses(List<StudyCafeLockerPass> passes) { this.passes = passes; } // 📝 lines을 해석하여 List<StudyCafeLockerPass> 객체를 만들어준다. public static ReadLockerPasses ofLines(List<String> lines) { List<StudyCafeLockerPass> passes = lines.stream() .map(ReadLockerPasses::ofLine) .toList(); return new ReadLockerPasses(passes); } private static StudyCafeLockerPass ofLine(String line) { String[] values = line.split(CSV_SPLITTER); // ⭐️ CSV라는 방식에 종속적 StudyCafePassType studyCafePassType = StudyCafePassType.valueOf(values[0]); int duration = Integer.parseInt(values[1]); int price = Integer.parseInt(values[2]); return StudyCafeLockerPass.of(studyCafePassType, duration, price); } // 📝 StudyCafeLockerPasses를 생성해준다. public StudyCafeLockerPasses toPasses() { return StudyCafeLockerPasses.of(passes); } }✏ 우빈님 리뷰Q. 일급 컬렉션을 적용하기 위해 toPasses() 메서드를 생성했는데 Read~라는 네이밍을 가진 클래스에 많은 책임이 부여된 것 같아 네이밍이 모호한 것 같습니다. 좋은 방법이 있을까요..? 🧐 A. 많은 책임이라고 생각하신 이유가 있을까요? "ReadLockerPasses는 어디선가 읽은 lines를 가지고 StudyCafeLockerPasses를 만들어준다"의 책임으로 보여서, 어색하지 않으며 테스트 코드 작성도 가능하다. 그와 별개로 CSV라는 방식에 종속되어있다. CSV형식이 다른 방식으로 바뀌었을 때 같이 바뀌어야 하는 부분이 CSV_SPLITTER 부분이다. 의도한 것 이라면 상관없다. 🤔 돌아보기Read라는 클래스명을 가지고 있어 StudyCafeLockerPasses를 생성해주는 메서드가 존재해 많은 책임이 있다고 생각했는데..우빈님 리뷰 이후에 다시 보니.. 그렇게 어색한가 싶기도 하다.. ㅎㅎ해당 클래스의 작성 당시 CSV 방식을 의존하려는 의도는 없었다. 단순히 읽은 부분의 개념을 추출한 것인데.. 위의 클래스는 CSV_SPLITTER상수가 사용되어 의도하지 않게 CSV라는 방식에 종속적이게 된 것이다.CSV라는 방식이 변경되면 객체 로직이 바뀌어야 한다.객체 구현 시, 종속성에 대해서 방어적으로 접근할 필요가 있어보인다.3⃣ ProvideException 커스텀 예외🔗 Github PR 링크ProvideException 커스텀 예외♻ 리팩토링 코드// 📝 이용권을 가져오는 과정에서 생긴 에러의 커스텀 예외 클래스 생성 public class ProvideException extends RuntimeException { public ProvideException(String message) { super(message); } } public class StudyCafePassMachine { public void run() { try { outputHandler.showPassOrderSummary(order); } catch (AppException e) { outputHandler.showSimpleMessage(e.getMessage()); } catch (ProvideException e) { // 📝 커스텀 예외 catch outputHandler.showSimpleMessage("이용권을 제공받을 수 없습니다."); } catch (Exception e) { outputHandler.showSimpleMessage("알 수 없는 오류가 발생했습니다."); } } }✏ 우빈님 리뷰Q. AppException 성격이랑 다른 것 같다고 생각되어 Provider 인터페이스에서 발생하는 예외 클래스 ProvideException를 별도로 생성하였습니다. A. 혹시 어떻게 다르다고 생각셨나요?? AppException의 의도는, 프로그램에서 발생할 수 있는 대부분의 애플리케이션 상황을 정의하는 최상위 예외 클래스이다. 만약 ProvideException을 별도로 표기하여 더 구체적인 상황을 나타내고 싶으면, AppException을 상속받아서 구성해야 한다. 그렇지 않으면 커스텀 예외 클래스가 늘어남에 따라 catch절도 같이 늘어날 것이다. 추가적으로, "이용권을 제공받을 수 없습니다."라는 메시지가 사용자 친화적이지 않다. 🤔 돌아보기리팩토링 당시, 초기 이용권을 가져와야만 프로그램이 실행된다는 관점에서 ProvideException의 커스텀 예외 클래스를 작성하였다.하지만 이용권을 가져오는 부분은 프로그램 내부에서 필요한 시점마다 호출하고 있어우빈님 리뷰대로 AppException 클래스를 상속받아서 작성하는 것이 더 나은 설계 같다.예외 메세지도 사용자 관점에서는 친화적이지 않은 것이 분명하다.내가 키오스크 시스템을 사용하다가 저런 메세지를 마주한다면... 화가 날 것 이다... 😡프로그램의 의도를 정확히 파악할 필요가 있어보인다. 또한 예외 메세지도 누가 보는지에 따라 고민해보는 습관을 길러야겠다.이렇게, 요청한 3개의 리뷰와 2개의 추가 리뷰를 받아 보았다..고작 3일 만에 7명이나 리뷰를 해주셨는데 세심하고 또 세심했다... 퀄리티가 상당했다.. ✨이번 온라인 라이브를 통해 우빈님에 대한 팬심과 존경심이 더욱 커졌다....! 📈리뷰해주신 내용으로 다시 리팩토링을 함으로써 한층 더 Readable Code에 대한 성장을 경험할 수 있었다. 🚀💡 자기만의 언어로 강의 키워드 정리하기 섹션 6. 코드 다듬기좋은 주석 - 주석의 양면성주석이 많다는 것 : 추상화가 덜 되고 가독성이 좋지 않은 코드 (코드 품질 저하 📉)주석이 필요한 경우 : 히스토리를 알 수 없을 경우, 주석으로 상세히 설명변수와 메서드 나열 순서변수 : 사용하는 순서대로 위치한다. (인지적 경제성 / 뇌 메모리 줄이기)객체의 공개/비공개 메서드 : 공개 메서드를 상단에 위치하고, 비공개 메서드 하단에 위치한다. 공개 메서드 중에서도 중요도의 순서에 따라 배치한다.공개 메서드 : 객체의 상태를 변경 하는 부분이 가장 상단에 위치하도록 - 상태 변경 >>> 판별 >= 조회비공개 메서드 : 출현한 순서대로패키지 나누기여러 파일들의 네임 스페이스를 관리하기 때문에 적당한 수준으로 잘 나누어야 한다.대규모 패키지 변경은 팀원과의 합의 필요 -> 추후 conflict가 생길 수 있다.기능 유지보수하기정렬 단축키, linting, style - sonarlint, editorconfig섹션 7. 리팩토링 연습메서드 추출로 추상화 레벨 맞추기Optionalreturn null / Optional 파라미터 사용은 안티패턴이다.객체에 메시지 보내기객체를 존중하고 메시지를 보내자.객체의 책임과 응집도⭐️ 추상화 관점의 차이 - FileHandler구현에 초점을 맞춘 추상화 VS 도메인 개념에 초점을 맞춘 추상화File을 read하는 부분의 로직들은 전부 FileHandler에 들어갈 것이다. 잘못된 객체 응집일 수도 있다..방법에 초점을 맞춘 설계 방식이 아닌 어떤 데이터를 가져오는 가에 대한 초점을 맞추는 것이 좋다.섹션 8. 기억하면 좋은 조언들능동적 읽기가지고 있는 리팩토링 기법들을 총동원해서 읽자. -> 리팩토링하면서 읽기눈으로만 보는 수동적 읽기는 권장하지 않는다.도메인 지식을 늘리기 위해서 능동적 읽기가 필요하다. (작성자의 의도 파악)오버 엔지니어링필요한 적정 수준보다 더 높은 수준의 엔지니어링예시 1. 구현체가 하나인 인터페이스구현체가 수정할 때마다 인터페이스도 수정해야 함코드 탐색의 어려움예시 2. 너무 이른 추상화정보가 숨겨지기 때문에 복잡도가 높아진다.후대 개발자들이 선대의 의도를 파악하기가 어렵다.은탄환은 없다클린 코드도 은탄환이 아니다.실무에서의 줄다리기지속 가능한 소프트웨어 품질 VS 기술 부채를 안고 가는 빠른 결과물 -> 클린 코드를 대비한 코드 센스가 필요하다.모든 기술과 방법론은 적정 기술의 범위 내에서 사용되어야 한다.항상 정답인 기술은 없다.한계까지 연습해보고, 적정 수준, 적정 시점을 깨닫는 것이 필요하다.섹션 3. 단위 테스트단위 테스트작은 코드(클래스 또는 메서드) 단위를 독립적으로 검증하는 테스트 -> 가장 기본이 되는 테스트검증 속도가 빠르고, 안정적수동 테스트, 자동화 테스트 -> 인지 필요사람이 검증하는 수동 테스트 -> sout으로 출력하고 눈으로 직접 확인기계가 검증하는 자동화 테스트Junit5, AssertJJunit5 : 단위 테스트를 위한 테스트 프레임워크AssertJ : 테스트 코드 작성을 원할하게 돕는 테스트 라이브러리 - 풍부한 API 메서드 체이닝 지원해피 케이스, 예외 케이스 -> 테스트 케이스 세분화예외 케이스 : 암묵적 혹은 드러나지 않은 요구사항에서 발견경계값 테스트범위, 구간, 날짜 경계값들로 테스트를 해야한다.테스트하기 쉬운/어려운 영역 (순수함수)테스트 하기 어려운 영역을 구분하고 분리하기외부로 분리할수록 테스트 가능한 코드는 많아진다.테스트하기 어려운 영역관측할 때마다 다른 값에 의존하는 코드 : 현재 날짜/시간, 랜덤 값, 전역 변수/함수, 사용자 입력외부 셰계에 영향을 주는 코드 : 표준 출력, 메시지 발송, 데이터베이스에 기록하기순수 함수 - 테스트하기 쉬운 영역같은 입력에는 항상 같은 결과외부 세상과 단절된 형태lombok@Data, @Setter, @AllArgsConstructor 지양양방향 연관관계 시 @ToString 순환 참조 문제섹션 4. TDD: Test Driven DevelopmentTDD 테스트 주도 개발 (Test Driven Development)로, 프로덕션 코드보다 테스트 코드를 먼저 작성하여 테스트가 구현 과정을 주도하도록 하는 방법론선 기능 구현, 테스트 작성의 문제점 (일반적인 개발) - 구현순서 : 기능 -> 테스트테스트 자체의 누락 가능성해피 케이스만 검증할 가능성잘못된 구현을 다소 늦게 발견할 가능성선 테스트 작성, 기능 구현 (TDD) - 구현순서 : 테스트 -> 기능복잡도(유연하며 유지보수가 쉬운)가 낮은 테스트 가능한 코드로 구현할 수 있게 한다.테스트가 힘든 코드를 위한 코드 작성이 가능예를 들면 LocalDateTime.now()의 경우 외부세계로 분리해서 테스트를 하기 편한 코드를 작성할 수 있다.프로덕션 코드를 작성한 후 테스트 코드를 작성하기 귀찮을수도..쉽게 발견하기 어려운 엣지 케이스를 놓치지 않게 해준다.구현에 대한 빠른 피드백을 받을 수 있다.과감한 리팩토링이 가능해진다.테스트 코드가 보장TDD의 관점이전의 관점 : 테스트는 구현부 검증을 위한 보조 수단변화된 관점 : 테스트와 상호 작용하며 발전하는 구현부레드 - 그린 - 리팩토링Red : 실패하는 테스트 작성Green : 테스트 통과 하는 최소한의 코딩Refactor : 구현 코드 개선 테스트 통과 유지애자일 방법론 vs 폭포수 방법론애자일 방법론 https://agilemanifesto.org/iso/ko/manifesto.html반복적 개발(Iterative Development): 짧은 개발 주기(스프린트)를 반복하며 지속적으로 개선.유연성: 요구사항 변경을 수용할 수 있도록 유동적으로 진행.고객 참여: 개발 과정에서 지속적인 피드백을 반영하여 사용자 중심 개발 가능.자율적인 팀 구성: 개발팀이 자체적으로 의사 결정을 하며, 빠르게 문제를 해결함.폭포수 방법론단계적 개발: 요구 분석 → 설계 → 구현 → 테스트 → 배포 → 유지보수 순서로 진행됨.문서 중심: 각 단계마다 문서화가 철저하게 이루어짐.선형 구조: 이전 단계가 완료되어야 다음 단계로 넘어갈 수 있음.변경이 어려움: 초기에 요구사항을 확정하면 이후 변경이 어렵고 비용이 많이 듦.익스트림 프로그래밍XP(Extreme Programming, 익스트림 프로그래밍)는 애자일 방법론 중 하나로, 빠른 개발 주기와 지속적인 피드백을 중심으로 하는 소프트웨어 개발 방법론이다. 고객의 요구사항 변화에 빠르게 대응할 수 있도록 짧은 개발 반복 주기(Iteration)와 강한 협업 문화를 강조한다.스크럼, 칸반스크럼애자일 프레임워크로, 일정한 스프린트 동안 작업을 계획하고 진행하는 반복적이고 점진적인 개발 방식이다. 짧은 개발 스프린트를 통해 빠르게 결과물을 만들고 지속적으로 개선하는 것이 핵심이다.1⃣ 백로그 작성 – 제품 백로그에 모든 요구사항을 정리2⃣ 스프린트 계획 – 스프린트 기간 동안 수행할 작업 선정3⃣ 스프린트 진행 – 개발 진행 및 매일 스탠드업 미팅4⃣ 스프린트 리뷰 – 개발 완료된 기능을 검토5⃣ 회고(Retrospective) – 개선점을 찾고 다음 스프린트에 반영칸반Workflow와 가시성을 중심으로 한 애자일 프레임워크로, 지속적인 개선과 작업량 관리를 중점적으로 다룬다. 작업을 시각적으로 표현하여 현재 진행 상황을 쉽게 파악할 수 있도록 합니다.1⃣ Backlog: 해야 할 작업을 모아둠2⃣ To Do: 현재 진행할 작업3⃣ In Progress: 진행 중인 작업4⃣ Review/Test: 리뷰나 테스트가 필요한 작업5⃣ Done: 완료된 작업섹션 5. 테스트는 []다.테스트 코드는 문서다.프로덕션 기능을 설명해주는 것이 테스트 코드 문서다.다양한 테스트 케이스를 통해 프로덕션 코드를 이해하는 시각과 관점을 보완할 수 있다.고민했던 내용(테스트 코드)을 팀 자산(소스 코드)으로 공유할 수 있다.@DisplayName - 도메인 정책, 용어를 사용한 명확한 문장메서드명만으로 어떤 것을 검증하고자 하는 의도 파악이 어려움Junit5에 추가한 어노테이션이다.문장 형태로 섬세하게 테스트 검증에 대한 내용을 어노테이션안에 작성한다.섬세한 DisplayName특정 시간 이전에 주문을 생성하면 실패한다. ❌영업 시작 시간 이전에는 주문을 생성할 수 없다. ✅도메인 용어를 사용하여 추상화된 내용을 담기 -> 메서드 자체의 관점 보다 도메인 정책 관점 (특정 시간 -> 영업 시작 시간 ✅)테스트의 현상을 중점으로 기술하지 말 것 (~실패한다 ❌)Given / When / Then - 주어진 환경, 행동, 상태 변화Given : 시나리오 진행에 필요한 모든 준비 과정When : 시나리오 행동 진행Then : 시나리오 진행에 대한 결과 명시, 검증TDD vs BDDBDDTDD에서 파생된 개발 방법시나리오 기반한 테스트 케이스 자체에 집중하여 테스트한다.Junit vs SpockSpock은 Groovy언어로 BDD 패턴을 적용해서 테스트 코드를 작성할 수 있다.언어가 사고를 제한한다.명확하지 못한 테스트 코드는 사고를 제한할 수 있다.문서로서의 테스트를 신경 쓸 필요가 있다. 🏃 돌아보며..미션과 발자국을 정신없이 진행하다 보니 벌써 남은 인프런 워밍업 클럽도 2주밖에 남지 않았다.처음에 OT 라이브 당시 러너가 120명 정도였는데, 이번 중간 점검 라이브 때는 60명 정도로 줄어들었다.강의 내용 자체는 어렵지 않지만.. 2개의 강의(14시간 + 12시간)를 한 달만에 들으면서 미션과 발자국을 진행하는 건 쉽지 않아 보인다..나는 미리 강의를 수강해서 다행이다라는 생각이 든다.. 😅하지만, 쉽지 않은 만큼 성실히 참여한다면 단기간 내 성장하는 데 큰 도움이 될 것이다.그리고 이번 주에 코드리뷰를 신청하기 잘했다는 생각이 들었다. 🍀위에서도 여러 번 언급했지만 우빈님의 세심한 리뷰 탓(?)에내가 미션을 수행하는 데 있어 우빈님보다 세심하게 집착 했었나..? 반성하게 된다... 😭마지막 주차 온라인 라이브에서도 테스트 코드에 대한 코드리뷰가 진행된다고 한다.기회가 된다면 또 한 번 코드리뷰를 받아 단골 손님이 되고 싶다. 😂2주를 걸쳐, 읽기 좋은 코드의 스터디 과정은 이번주로 막을 내렸다.다음 주차부터는 테스트 코드에 대해 본격적으로 스터디하는 과정이 진행된다.남은 2주도 화이팅하며 좋은 성장을 이루길 기대해 본다. 🔥끝으로, 3월 중순이 되니 이제 슬슬 봄 내음이 나는 것 같다.. 🌸얼어붙은 개발 시장에도 봄이 찾아왔으면 좋겠다.. 🧊발자국 2주차 끄읕 ![출처]인프런 워밍업 클럽 : https://www.inflearn.com/course/offline/warmup-club-3-be-code강의 : https://www.inflearn.com/course/readable-code-%EC%9D%BD%EA%B8%B0%EC%A2%8B%EC%9D%80%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1%EC%82%AC%EA%B3%A0%EB%B2%95/dashboard

백엔드인프런워밍업클럽백엔드3기발자국박우빈클린코드읽기좋은코드

10 14일 전
10

살짝 서두른 Spring 기반 테스트 코드 - 인프런 워밍업 클럽 3기 백엔드 코드✨

인프런 워밍업 클럽 3기 백엔드 코드 발자국 1주차인프런 워밍업 클럽 3기 백엔드 코드 발자국 2주차인프런 워밍업 클럽 3기 백엔드 코드 발자국 3주차🎼 알레그레토 : 조금 빠르게 3주 차부터는 미션과 발자국을 조금 서둘러 진행할 예정이다.앞서 언급했듯이, 이번 주부터 항해 플러스가 시작되어 최대한 진도를 빠르게 빼보려고 한다.현재 날짜(3월 17일) 기준으로 Day 16 미션과 3주 차 발자국을 작성 중이며,이번 주 안에 Day 18 미션과 4주 차 발자국도 작성을 완료하는 것이 목표이다.(가능할지는 모르겠지만 ㅎㅎ)조금 급하게 진행하는 감이 있어 개인적으로 많이 아쉽다.워밍업 클럽에만 온전히 집중할 수 있었다면 더 많은 성장을 할 수 있었을 텐데 말이다...하지만, 후회는 하지 않는다. 다음 기수의 존재는 우빈님만 아시겠지만, 다음에 또 없을 수도 있는 기회를 놓치고 싶지 않았기 때문이다. 👍나는 위의 이미지처럼 Trello를 활용해 인프런 워밍업 클럽에 참여하고 있다.미션 제출 날짜가 일정하지 않다 보니, 제출 하루 전에 노티를 받도록 설정해 두고 유용하게 사용 중이다. 🙃💡 자기만의 언어로 키워드 정리하기 섹션 6. Spring & JPA 기반 테스트Layered Architecture레이어드 아키텍처의 단점 : 기술에 대한 강결합이 심하다는 단점이 존재Hexagonal Architecture도메인 모델은 외부의 것들을 아예 모른다.도메인 모델 중심 (멀티 모듈 및 시스템이 커진다면..)단위테스트 vs. 통합테스트단위테스트 만으로는 커버하기 어려운 영역이 존재 (여러 모듈 및 여러 객체가 협력하기 때문에)통합테스트란?여러 모듈이 협력하는 기능을 통합적으로 검증하는 테스트단위 테스트만으로는 기능 전체의 신뢰성을 보장할 수 없다.IoC, DI, AOPORM, 패러다임의 불일치, HibernateSpring Data JPAQueryDSL@SpringBootTest vs @DataJpaTest@DataJpaTest는 @SpringBootTest보다 가볍다.@DataJpaTest보다는 @SpringBootTest를 더 선호@DataJpaTest는 @Transactional이 있어 롤백이 된다.@SpringBootTest는 클렌징을 해주어야 한다.@SpringBootTest vs @WebMvcTest@SpringBootTest는 E2E 테스트, 즉 통합테스트 시 사용하는 어노테이션이다.@WebMvcTest는 Presentation Layer에 대한 단독 테스트시 사용하는 어노테이션이다.다른 레이어들은 mocking을 통해 동작을 제어한다.@Transactional(readOnly = true)테스트에서 사용 시, 롤백 되는 것에 유의 해야 한다.트랜잭션 경계 설정을 해야한다.엔드포인트를 잘 설계해야 한다.Optimistic Lock, Pessimistic Lock낙관적 락 : 데이터 충돌이 자주 발생하지 않을 것이라 낙관적으로 가정하고, 트랜잭션을 진행하는 방식데이터를 읽을 때는 락을 걸지 않고, 데이터를 업데이트 시 버전 비교하여 충돌 여부 판단성능 저하를 최소화, 동시성을 높이는 데 유리비관적 락 : 데이터 충돌이 자주 발생할 것이라 비관적으로 가정하고, 트랜잭션이 데이터를 사용할 때 미리 잠금을 거는 방식데이터 일관성을 유지하는 데 초점트랜잭션이 완료될 때까지 다른 트랜잭션이 데이터를 수정할 수 없음데드락 발생 가능CQRS명령 조회 책임 분리 : Command Query Responsibility Segregation읽기(조회)와 쓰기(명령)의 책임을 분리하는 소프트웨어 아키텍처 패턴장점성능 최적화확장성 증가데이터 모델 최적화비지니스 로직의 명확한 분리단점복잡성 증가데이터 동기화 문제트랜잭션 관리 어려움@RestControllerAdvice, @ExceptionHandler@RestControllerAdvice : ControllerAdvice의 기능을 하는데 JSON으로 응답을 해주는 Advice커스텀 예외를 던지고 @RestControllerAdvice의 @ExceptionHandler에서 예외를 처리할 수 있다.Spring bean validation@NotNull, @NotEmpty, @NotBlank도메인 요구사항에서 나오는 validation과 책임 분리해야한다.Controller 단에서는 최소한의 validation을 통한 검증이 이루어져야 한다.@WebMvcTestObjectMapperJackson 라이브러리에서 제공하는 클래스로, Java 객체와 JSON 간의 변환을 담당하는 역할직렬화, 역직렬화를 수행Mock, Mockito, @MockBeanMock : 실제 객체 없이 동작을 모방하여 단위 테스트를 수행하는 가짜 객체Mockito : Java에서 Mock 객체를 쉽게 생성하고 관리할 수 있는 라이브러리@MockBean : Spring 컨텍스트에 Mock 객체를 등록하여 실제 빈을 대체@Mock : 순수한 자바에서 Spring 컨텍스트가 필요하지 않을 때 사용@MockBean : Spring 컨텍스트에서 특정 빈을 Mocking 하고 싶을 때 사용👨🏻‍💻 미션 회고[미션 Day 11][미션 PR]#6스터디 카페 이용권 선택 시스템 단위 테스트 작성미션 조건은 다음과 같다.✔각 프로젝트 모두 강의 중에 작성한 tobe 패키지 코드를 기준으로 함 (lesson 6-4 가 가장 마지막 버전)✔3개 이상의 서로 다른 클래스 & 총 7개 이상의 테스트 작성 ➡ 단, 같은 인터페이스를 구현하고 있는 구현체들은 1개 클래스로 간주한다.✔무엇을 테스트하고자 했는지를 잘 나타낸 @DisplayName 작성하기✔BDD(given/when/then) 스타일 따르기 (주석으로 표기)가장 작은 단위의 메서드 부터 단위 테스트를 작성하면서,테스트 커버리지를 높이기 위해, 모든 주요 로직에 대한 단위테스트를 작성하고자 했다.단위테스트를 작성하면서 강의에서도 강조한 내용 중에 하나인 @DisplayName을 잘 작성하기 위해 많이 고민했다.'권'이라는 Pass의 의미를 '패스'로 통일하여 일관성있게 작성하였다.읽는 사람으로 하여금 뇌 메모리를 적게 쓰게 하기 위해 @DisplayName도 최대한 추상화해서 작성하려고 노력했다.테스트의 현상을 중점적으로 작성하지 않으려고 하였다.하지만, 사용자 입력을 받는 클래스인 InputHandler의 단위테스트를 작성하는 과정에서 어려움이 있었다.InputHandler 내부에서 static으로 선언되어 있어 mocking도 하기 어려웠다.public class InputHandler { private static final Scanner SCANNER = new Scanner(System.in); }프로덕션 코드를 수정하지 않는다는 요구사항을 지키면서는 테스트가 어려웠다. 만약 프로덕션 코드를 수정한다면 테스트가 가능해질 것이다.👆 Scanner를 외부세계로 분리하면 테스트가 가능할 것 같다.✌ InputHandler 상위의 인터페이스를 생성 후, 테스트 전용 support 성격의 구현체를 만들어서 테스트가 가능할 것 같다. 미션 제출 후, 차후에 프로덕션 코드를 수정해서 테스트를 작성 해볼 예정이다.🏃 돌아보며..Day 11 미션은 제출이 끝이 아니라, 중간점검 라이브에 이어 마지막 라이브에서 코드 리뷰 단계가 남아있다.중간 점검 때 세심한 코드 리뷰를 받고 수정하면서 많은 성장을 했기에 이번에도 신청하지 않을 이유가 없었다.다른 러너 분들도 꼭 받아봤으면 한다. 😊아직까지 혼자 신청해서 뻘줌해서가 아니라... 진짜 좋은 기회이자 경험이다... 🤣코드 리뷰 대상자로 뽑히길 기대하며.. 마지막 4주차도 화이팅 💪[출처]인프런 워밍업 클럽 : https://www.inflearn.com/course/offline/warmup-club-3-be-code강의 : https://www.inflearn.com/course/readable-code-%EC%9D%BD%EA%B8%B0%EC%A2%8B%EC%9D%80%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1%EC%82%AC%EA%B3%A0%EB%B2%95/dashboard

백엔드인프런워밍업클럽백엔드3기발자국박우빈테스트코드실용적인테스트가이드

10 7일 전
치현

[인프런 워밍업 스터디 클럽 3기 풀스택] 2주차 발자국

학습 내용인프런 워밍업 클럽 스터디 2주차로, 이번 주는 드롭 박스 프로젝트와 함께 Supabase의 Storage를 다뤄볼 수 있는 시간이었다. Supbase Storage1. 기본 구성 요소Files: 모든 종류의 미디어 파일 저장 가능 (이미지, GIF, 비디오 등)Folders: 파일을 체계적으로 구성하기 위한 디렉토리 구조Buckets: 파일과 폴더를 담는 최상위 컨테이너 (접근 규칙별로 구분)2. 접근 제어 모델Private Buckets (기본값) RLS(Row Level Security) 정책을 통한 접근 제어JWT 인증 필요Signed URL을 통한 임시 접근 가능Public Buckets파일 조회 시 접근 제어 없음URL만 있으면 누구나 접근 가능업로드/삭제 등 다른 작업은 여전히 접근 제어 적용3. 보안 기능RLS 정책 설정 가능SELECT (다운로드)INSERT (업로드)UPDATE (수정)DELETE (삭제)소유권 관리owner_id 필드로 리소스 소유자 추적JWT의 sub claim 기반 소유권 할당4. 이미지 변환 기능 (Pro Plan 이상)실시간 이미지 최적화크기 조정품질 조정 (20-100)WebP 자동 최적화변환 옵션resize 모드: cover, contain, fillwidth/height 지정 (1-2500px)최대 파일 크기: 25MB최대 해상도: 50MP5. 인증 방식S3 액세스 키서버 사이드 전용모든 버킷에 대한 완전한 접근 권한세션 토큰클라이언트 사이드 사용 가능RLS 정책 기반 제한된 접근6. 통합 기능Next.js 이미지 로더 지원AWS S3 호환성PostgreSQL DB와 연동7. 제한사항파일명은 AWS S3 명명 규칙 준수 필요HTML 파일은 보안상 plain text로 반환이미지 변환 기능은 Pro Plan 이상에서만 사용 가능미션 2 구현 내용과제 구현 저장소Dropbox 중파일의 마지막 수정(업로드) 시간을 표시하는 기능 추가 하기 export interface FileObject { name: string bucket_id: string owner: string id: string updated_at: string created_at: string last_accessed_at: string metadata: Record<string, any> buckets: Bucket }=> DropboxImage 컴포넌트가 prop으로 받는 image의 타입은 FileObject로 그 중 업로드시간은 created_at을 의미하기에 이를 이미지에 추가하였다.(사진 참고) 포인트 1: 한글 파일명 es-hangul 사용// 안전한 파일명 생성을 위한 유틸리티 export class FileNameConverter { // 안전한 문자 패턴 정의 private static readonly SAFE_CHARACTERS = /^[a-zA-Z0-9!\-_.*'()]+$/; private static generateRandomString(length: number = 8): string { const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; return Array.from({ length }, () => chars.charAt(Math.floor(Math.random() * chars.length)) ).join(""); } // 파일명이 안전한 문자들로만 구성되었는지 확인 private static isSafeFileName(name: string): boolean { return this.SAFE_CHARACTERS.test(name); } // 안전하지 않은 문자를 포함한 파일명을 안전한 형식으로 변환 private static convertToSafeFileName(name: string): string { try { // 파일명 정규화 const normalized = name.trim().normalize(); // 한글이나 특수문자가 있는지 확인 const hasKorean = /[ㄱ-ㅎㅏ-ㅣ가-힣]/.test(normalized); const hasSpecialChars = /[^A-Za-z0-9]/.test(normalized); if (!hasKorean && !hasSpecialChars) { return normalized; } // 한글이 있는 경우 로마자로 변환 시도 if (hasKorean) { const romanized = romanize(normalized); if (romanized && romanized !== normalized) { // 로마자 변환 결과에서 안전하지 않은 문자 제거 return romanized.replace(/[^a-zA-Z0-9]/g, "_").toLowerCase(); } } // 변환 실패 시 랜덤 문자열 생성 return this.generateRandomString(); } catch (error) { console.error("Conversion error:", error); return this.generateRandomString(); } } // 원본 파일명을 안전한 형식으로 변환 static encode(fileName: string): string { console.log("Original filename:", fileName); const extension = fileName.split(".").pop() || ""; const nameWithoutExt = fileName.slice(0, fileName.lastIndexOf(".")); const safeName = this.isSafeFileName(nameWithoutExt) ? nameWithoutExt : this.convertToSafeFileName(nameWithoutExt); console.log("Safe filename:", safeName); return `${safeName}_${Date.now()}.${extension}`; } // 파일명에서 타임스탬프 제거하여 원본 이름 추출 static decode(fileName: string): string { const [name] = fileName.split("_"); return name || fileName; } }포인트 2 : 업로드 날짜 표시export function formatDate(timestamp: string): string { const date = new Date(timestamp); const now = new Date(); const diff = now.getTime() - date.getTime(); // 1일 이내 if (diff < 24 * 60 * 60 * 1000) { const hours = Math.floor(diff / (60 * 60 * 1000)); if (hours < 1) { const minutes = Math.floor(diff / (60 * 1000)); return `${minutes}분 전`; } return `${hours}시간 전`; } // 30일 이내 if (diff < 30 * 24 * 60 * 60 * 1000) { const days = Math.floor(diff / (24 * 60 * 60 * 1000)); return `${days}일 전`; } // 그 외 return date.toLocaleDateString("ko-KR", { year: "numeric", month: "long", day: "numeric", }); } 회고파일명 변환하는데 생각보다 시간이 많이 소요됐다.여찌저찌 구현은 헀지만, 이미지가 어떻게 encoding되고 decoding되는지 일련의 과정에 대한 공부가 필요함을 느끼는 이번주 였다.  

풀스택풀스택인프런워밍업스터디클럽Next3기SupabaseReact프론트엔드2주차발자국

10

[인프런 워밍업 클럽 3기 - 백엔드 코드] 발자국 1주차

🤔 인프런 워밍업 클럽 스터디 신청 계기나는 인프런 워밍업 클럽 스터디 신청하기 한 달 전에 스터디 과정에 쓰이는 로드맵 강의를 이미 완강하였다.그럼에도 불구하고 강의를 수강하기 전부터 우빈님의 팬(?)이였기 때문에 이메일을 받자마자 신청을 할 수 밖에 없었다.항해 플러스 8기 백엔드 과정을 앞두고 약 2주간 겹치는 구간이 있어 잠시나마 고민을 하긴 했지만?..복습 차원에서 미션과 발자국을 통해 학습내용을 더 딥하게 파고들어 성장하고자 신청하게 되었다.👣 발자국이란?인프런 워밍업 클럽에서 이야기하는 발자국이란 다음과 같다.주 1회, 일주일 동안 배운 내용을 바탕으로 남기는 학습 일기이자 회고이다.매주 강의를 요약하고 기록하면서 강의 내용을 제대로 이해했는지 확인하고, 보완할 부분을 찾는다.기록을 통해 배운 내용에 대해 메타인지 하는 시간을 가진다.나는, 이번 과정에서는 강의를 재수강하지는 않고섹션 별로 우빈님께서 제공해주신 키워드 정리를 통해 학습을 진행할 예정이다.💡 자기만의 언어로 키워드 정리하기섹션 2. 추상추상과 구체 : 이름 짓기 + 메서드 선언부 = 추상화 행위실제 코드를 작성하다보면 이름 짓기에 시간을 가장 많이 투자한다.이름 짓기의 중요성이 크기 때문에 반복되는 훈련을 통해 이름 짓기의 스킬 향상이 필요해 보인다. 추상화 레벨메서드를 추출하는 과정에서 외부셰계와 내부세계가 분리가 되고, 이 과정에서 추상화 레벨을 잘 고려해야한다.추상화 레벨이 동등해야 자연스럽게 코드가 읽힌다.[다른 추상화 레벨]public class Order { public void process() { paymentService.pay(); // 고수준 추상화 📈 System.out.println("결제 완료"); // 저수준 추상화 📉 } } [동등한 추상화 레벨]public class Order { public void process() { paymentService.pay(); // 고수준 추상화 📈 printCompletedPay(); // 메서드를 분리하여 고수준의 추상화를 동등하게 유지 ✅ } private void printCompletedPay() { System.out.println("결제 완료"); } } 매직 넘버 + 매직 스트링 : 상수 혹은 Enum을 활용하여 이름 짓기를 통해 가독성이 좋아지게 한다.섹션 3. 논리, 사고의 흐름뇌 메모리 적게 쓰기 (인지적 경제성)읽기 좋은 코드는 읽는 사람으로 부터 뇌를 편안하게 한다.반대로 읽기 힘든 코드는 읽는 사람의 뇌를 피곤하게 한다. Early return : Early return은 아래쪽 내용을 읽을 필요 없어 뇌 메모리 적게 쓰기에 효과적이다.사고의 depth 줄이기중첩 분기문가 중첩 반복문을 무조건 depth를 줄이기보다, 추상화를 통한 사고의 depth를 줄이는 것이 중요하다.사용할 변수는 가깝게 선언한다.공백 라인 : 공백 라인도 리팩토링 시 중요한 요소이다. 공백 라인이 존재하지 않은 코드는 읽기 어렵다.부정어부정연산자는 가독성을 해친다. 여러 번의 사고를 거치면서 코드를 읽어야 한다.한 번에 사고할 수 있도록 부정연산자를 제거하고 메서드로 추출했다면 이름 짓기를 적절하게 수정해야한다.해피 케이스, 예외 처리해피 케이스보다 예외처리에 더 신경써서 코드를 작성해야 한다.예외는 의도하지 않은 예외랑 의도한 예외가 있다.섹션 4. 객체 지향 패러다임객체, 협력과 책임, 관심사의 분리, 높은 응집도와 낮은 결합도협력과 책임 : 객체간의 협력, 객체가 담당하는 책임관심사의 분리 : 관심사에 따라 객체를 만들어 낼 수 있다.높은 응집도 : 특정한 관심사로만 이루어진 설계낮은 결합도 : 각 관심사끼리는 독립적이여야 한다.[낮은 응집도]public class OrderService { public Order createOrder(Product product) { // 주문 생성 로직 } public void sendEmail(Order order) { // 이메일 전송 로직은 OrderService의 관심사가 아니다. ❌ } } [높은 응집도로 해결]객체를 분리함으로써 응집도가 올라간다.public class OrderService { public Order createOrder(Product product) { // 주문 생성 로직 } } public class OrderEmailSender { // 객체를 분리하여 높은 응집도로 설계 ✅ public void sendEmail(Order order) { // 이메일 전송 로직 } } [높은 결합도]public class OrderService { private EmailService emailService = new EmailService(); // 높은 결합도 ❌ } [낮은 결합도로 해결]인터페이스와 DI를 활용하여 결합도를 낮춘다.public interface EmailSendable { } public class OrderEmailSender implements EmailSendable { } public class OrderService { private final EmailSendable emailSendable; public OrderService(EmailSendable emailSendable) { this.emailSendable = emailSendable; // 높은 결합도를 해결한다. ✅ } }객체끼리의 협력과 책임을 통해 프로그램을 만들 수 있다.가장 중요한 것은 관심사의 분리이고 높은 응집도와 낮은 결합도를 가진 설계를 가지는 것이 중요하다. getter/setter 자제하기, 객체에 메시지 보내기setter는 최대한 지양하는 것이 좋다.getter를 남발하는 건 객체를 존중하지 않는 것이며 무례하면서 폭력적인 행위이다.SOLID: SRP, OCP, LSP, ISP, DIP아래의 미션 Day 4 과정에 대한 내용을 통해 자세히 다룬다.DI/IoC섹션 5. 객체 지향 적용하기상속과 조합 : 상속은 결합도가 높기 때문에 조합을 활용하는 것이 더 좋은 설계이다.Value Object, EntityVO는 불변성, 동등성, 유효성을 보장해야하며 도메인의 개념을 추상화한 객체이다. (ex. Money 객체)Entity는 식별자가 존재한다. 식별자가 같으면 동등한 객체로 취급한다.일급 컬렉션 : 컬렉션을 Wrapping한 객체를 뜻한다. 컬렉션을 추상화하여 의미를 담을 수 있고, 가공 로직의 보금자리가 생긴다. (VO와 비슷하다.)Enum : 상수의 집합, 상수들에 대한 로직을 담을 수 있다.추상화와 다형성 활용하여 반복되는 if문 제거 -> OCP 지키기변하는 것 : 조건 & 행위 (구체)변하지 않는 것 : 조건을 만족하는가? / 행위를 수행한다. (추상)변하는 것과 변하지 않은 것을 구분해서 보는 훈련이 필요하다.숨겨져 있는 도메인 개념 도출하기 : 변경이 많이 일어날 것 같은 미래를 예견하고 이런 것을 도입해보면 어떨까? 하고 숨겨진 도메인 개념을 도출도메인 지식은 만드는 것이 아니라 발견하는 것이다.객체지향은 흉내내는 것이다.미래를 예견하고 도메인 개념을 도출해보자.[이메일 전송 개념에서 미래를 예견하고 숨겨진 도메인 개념을 도출]현재의 최선에서는 이메일만 발송하지만 미래에 카카오톡 알림톡이나 혹은 다른 알림에 대한 도메인 개념을 도출해보자public interface Sendable { // 미래를 예견하고 도매인 개념을 도출 ✅ void send(String message); } public interface EmailSendable extends Sendable { @Override void send(String message); }👨🏻‍💻 미션 회고 [미션 Day 2]미션 PR : https://github.com/discphy/warm-up-backend-code/pull/11⃣ 추상과 구체 예시강의 내용에서도 계속 등장하듯이 추상과 구체는 이번 강의에서 가장 핵심이 되는 단어들이다.이 미션을 접했을 때, 단순히 추상과 구체에 대한 예시를 들기보다 개발적인 관점에서 추상과 구체에 대해 접근하려고 노력했다. (적절한 예시인지는 잘 모르겠으나..😂)해당 미션을 통해, 추상화가 단순히 쉽지 않다는 걸 느꼈으며 개발적인 관점에서도 더 읽기좋은 코드를 작성하기 위해선 추상화를 잘 해야겠다라는 생각이 들었다. [미션 Day 4]미션 PR : https://github.com/discphy/warm-up-backend-code/pull/21⃣ validateOrder 메서드를 읽기 좋은 코드로 리팩토링 하기해당 메서드를 봤을 때는 엄청 어지러웠다.. 🤣 본능적으로 빨리 리팩토링 하고 싶다는 생각이..추상화 기법들을 통해 도메인 객체를 분리하고 도메인 내부세계로 검증 로직을 구현하였다.객체 분리 : 일급 컬렉션으로 의미있는 Items의 객체로 분리하였고, Member라는 객체를 생성하여 customerInfo에 대한 검증 로직을 구현하였다.사고의 depth 줄이기, 부정 연산자 지양 : 중복 분기문과 부정 연산자를 포함한 조건식을 리팩토링하여 읽는 사람으로 하여금 뇌 메모리 적게 쓰게 끔 리팩토링했다.미션 PR에도 코멘트를 달았지만 도메인 레이어의 로깅이 하는 구현 부가 포함된 게 신경이 쓰인다.도메인 레이어에서 의존성을 주입받지는 않아 객체 분리가 맞는지 모호하다.[기존 코드]public boolean validateOrder(Order order) { if (order.getItems().size() == 0) { log.info("주문 항목이 없습니다."); return false; } else { if (order.getTotalPrice() > 0) { if (!order.hasCustomerInfo()) { log.info("사용자 정보가 없습니다."); return false; } else { return true; } } else if (!(order.getTotalPrice() > 0)) { log.info("올바르지 않은 총 가격입니다."); return false; } } return true; }[리팩토링 이후 코드]public class Order { private static final Logger log = Logger.getLogger(Order.class.getName()); private final Items items; private final Member member; private final int totalPrice; private Order(Items items, Member member, int totalPrice) { this.items = items; this.member = member; this.totalPrice = totalPrice; } public boolean validate() { if (items.isEmpty()) { log.info("주문 항목이 없습니다."); return false; } if (totalPrice <= 0) { log.info("올바르지 않은 총 가격입니다."); return false; } if (member.hasNotInfo()) { log.info("사용자 정보가 없습니다."); return false; } return true; } } 2⃣ SOLID에 대하여 자기만의 언어로 정리사실, 객체 지향에 대해 학습한 사람들 중 SOLID를 모르는 사람은 없을 것 이다.그치만 여기서의 포인트는 자기만의 언어이다.위에서도 강의에 대한 정리내용을 작성할 때 "💡 자기만의 언어로 키워드 정리하기" 라는 제목으로 작성하였다.학습한 내용을 온전히 내 것으로 만들기 위해선 반드시 나의 언어로 작성해야 내 것이 되고, 차후에 정리한 내용을 다시 봤을 때도 금방 기억을 더듬을 수 있을 것 이다.그래서 나는 텍스트 보다 코드로 봤을 때의 이해도가 더 빠르기 때문에 SOLID 원칙의 내용을 정리할 때 불필요한 텍스트를 줄이고 코드로 내용을 정리하였다.🏃 돌아보며..위에서도 언급했지만, 항해 플러스 백엔드 스터디 2개도 병행하고 있어 참여를 망설였지만.. 신청하기 잘한 것 같다.(2기가 마지막이 될까봐 조마조마 했던 1인.. 우빈님 감사합니다.. 🙇‍♂)온라인 밋업을 통한 우빈님과 소통하는 것도 그렇고..여러가지 미션의 내용도 단조롭지 않고 수준이 높은 것 같아 다시 한번 메타인지를 경험하게 해준다.강의의 경우, 시간상 처음부터 끝까지 다시 보지는 못하겠지만.. 불과 몇 개월 전 수강한 강의임에도 단어들이 어색하다... 😂어색했던 부분은 다시 강의를 보며 이해하고 보완하였다.읽기 좋은 코드를 작성하기 위해 학습한 내용을 기반으로 적절한 추상화를 적용하는 훈련을 하며 내 코드가 읽기 좋은 코드가 되도록 노력해야겠다. [출처]인프런 워밍업 클럽 : https://www.inflearn.com/course/offline/warmup-club-3-be-code강의 : https://www.inflearn.com/course/readable-code-%EC%9D%BD%EA%B8%B0%EC%A2%8B%EC%9D%80%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1%EC%82%AC%EA%B3%A0%EB%B2%95/dashboard 

인프런워밍업클럽백엔드3기발자국

10 20일 전
천준민

워밍업 클럽 3기 BE 클린코드&테스트 - 1주차 발자국

📖 강의 요약 2⃣ 추상우리가 클린 코드를 추구하는 이유"가독성"혼자만 사용한다면 아마 필요 없는 이야기 일수도 있을 것이다 하지만 개발자는 협업이 중요하다고 들었다.나만 코드를 읽는 것이 아니라 협업을 하는 사람들끼리 컨벤션도 중요하지만 그 전에 가독성 있는 코드를 짜면 원활한 작업을 할수 있을 것이라고 생각한다. 추후 발생하는 비용들도 줄어들 것이다.이번 섹션에서 강조하는 것은 제목처럼 추상인 것 같다.추상을 알아보자"추상"중요한 정보는 가려내어 남기고 덜 중요한 정보는 생략하여 버린다.미션에서 제출한 내가 이해한 추상과 구체를 위와 같이 예시를 들수 있을 것 같다.[추상] 인프런 워밍업 클럽 3기 BE 클린코드 & 테스트를 수료 한다. [구체] 2 개 강의(Readable Code: 읽기 좋은 코드를 작성하는 사고법&Practical Testing: 실용적인 테스트 가이드 )를 100 % 수강한다. 주 1회(총 4회) 발자국 작성을 한다. OT를 포함한 총 3회 온라인 라이브를 출석한다 미션 6개 중 5개 이상 기한 내 제출 완료한다.추상화의 가장 대표적인 행위는 이름 짓기,메서드 선언, 동등한 추상화 레벨, 매직 넘버,매직 스트링 제거 등 이 있다.위 주제에서 공통적으로 이름을 잘 짓는 것이 기반이 되는 것 같다. ex) 변수이름, 메서드 이름, 상수 이름 3⃣ 논리, 사고의 흐름이번 섹션에서 한마디로 정리하자면 빼고 생각하자 였다. (로직이 복잡하면 그 내가 먼저 생각 할 수 있는 상황부터 먼저 거르고 생각하기)추가적으로 부정어, 예외처리, null,optional에 대해 한번 더 생각해보고 작성해보기 4⃣ 객체 지향 패러다임이번 섹션은 좀 반성을 좀 하였다. 특히 강의 중 getter 사용 에 대한 이야기 였는데 무조건 getter를 사용하면 객체안에 정보를 가져올수 있잖아라고 생각하는 나에게 생각을 많이하게 되었다.핵심 : 관심사를 분리, 높은 응집도, 낮은 결합도, getter를 사용하기 전에 객체의 메시지를 보내서 그 값을 사용할수 있는 지 부터 생각하는 능력 기르기 SOLID 원칙✅SRP : 하나의 클래스는 단 한 가지 변경 이유(= 책임) 만을 가져야 한다.✅OCP: 확장에는 열려 있고, 수정에는 닫혀 있어야 한다.✅LSP: 자식 클래스는 부모클래스의 책임을 준수하며, 부모 클래스의 행동을 변경하지 않아야 한다.✅ISP : 클라이언트는 자신이 사용하지 않는 인터페이스에 의존하면 안된다.✅DIP : 상위 수준의 모듈은 하위 수준의 모듈에 의존해서는 안된다. 5⃣ 객체 지향 적용하기이번 섹션은 내가 잘 이해를 잘못한 것 같아 다시 보아야 할 것 같다. 지금은 일기 정도로 작성 하지만 이해하여 꼭 적어보자강의를 듣고 옛날에 내가 작성한 코드(전체 코드 x)가 생각 났다.public enum ReservationStatus { PENDING("PENDING") { @Override public boolean canChangeTo(ReservationStatus status) { switch (status) { case APPROVED, CANCELED, EXPIRED -> { return true; } } return false; } }, APPROVED("APPROVED") { @Override public boolean canChangeTo(ReservationStatus status) { return status.equals(APPROVED); } }, CANCELED("CANCELED") { @Override public boolean canChangeTo(ReservationStatus status) { return status.equals(CANCELED); } }, EXPIRED("EXPIRED") { @Override public boolean canChangeTo(ReservationStatus status) { return status.equals(EXPIRED); } };지금 보니 메서드 명도 바꿀 필요가 있고 switch 사용으로 유지보수 어려움, if-else 사용 등 고쳐야할 부분이 많은 것 같다.목표 : 강의를 어느 정도 이해하여 코드를 바꿔보기 💡 미션 코드와 설명을 보고, [섹션 3. 논리 의 사고 흐름]에서 이야기 하는 내용을 중심으로 읽기좋은 코드로 리팩토링 해보기 블로그: https://zunmin4030.tistory.com/56 먼저 사이즈가 0이면 어떤 행위를 하고 0보다 크면 어떤 행위 0보다 작으면 어떤 행위를 한다라는 접근으로 코드를 이해 하는 것 부터 시작하였다. 거기에 대한 궁금증을 가지고 섹션별 내가 적용할 수 있는 부분을 생각해보았다. 이름짓기 , 부정 연산자 제거, Early return을 생각하여 과제를 해결하다보니 처음 코드를 말로 정리했을 때 생긴 궁금증을 해소 할수 있었다. 💬 회고 👍 잘한 점배운 것을 하나씩 적용해 보며 과제를 수행 해 나갔다.😮‍💨 아쉬웠거나 보완하고 싶은 점섹션 5에 대한 이해를 하지 못해 한번 더 공부 해야겠다. 📎출처Readable Code: 읽기 좋은 코드를 작성하는 사고법https://www.inflearn.com/course/readable-code-%EC%9D%BD%EA%B8%B0%EC%A2%8B%EC%9D%80%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1%EC%82%AC%EA%B3%A0%EB%B2%95/dashboard

백엔드인프런워밍업클럽backend3기

보키

[인프런 워밍업 클럽 3기 - BE/Project] 2주차 회고 발자국 🐾

1주차와 마찬가지로 KPT 회고 프레임워크를 선택해서 작성해보려고 한다! Keep(만족, 지속하고 싶은 부분)이번 2주차는 개인프로젝트에서는 RDB 모델링을 마친 뒤 엔티티를 정의했으며 현재는 조회 API를 만들고 있고, 강의에서는 Presentation Layer에 대한 부분을 수강했다. 강의가 개인프로젝트보다 좀 빠르지만, 그만큼 개인 프로젝트에 적용할 수 있는 부분에 대한 인사이트를 미리 얻어갈 수 있는 것 같아서 좋다.Problem(부족, 아쉬웠던 부분)또 내 과욕이 부른 스불재(스스로 불러온 재앙) 느낌의 아쉬운 부분을 써보자면.. RDB를 너무 현업과 비슷하게 만드는 것을 목표로 잡지 않았나 싶다..ㅎㅎ 뭐 어쩌겠어~ 벌린 일이니 해야지..!!강의에 대한 내용은 아쉬운게 전혀 없다!! JSP에서 Tag(<% %>)가 빠진, Spring 공식 팀에서도 밀고 있는 템플릿 엔진인 Thymeleaf를 잘 알고 계시고, 다시 배워보니 좋았다!여담으로, Template for Spring으로는 JSP / Thymeleaf / FreeMarker / Groovy Markup / Jade4j / JTE(Java Template Engine) / Velocity / JMustache / Pebble가 있다.어느것을 택하든 자유이나 Spring에서 공식적으로 Support하고 Advertise하는 제품(라이브러리)를 사용하는 것을 추천한다.Try(도전, P에 대한 해결책 등으로 다음번에 보완하거나 시도할 부분)엔티티를 너무 많이 만들고 관계도 1:N, N:1, N:M(을 풀기위한 중간 엔티티) 여러가지를 사용한만큼 JPA를 잘 알고 사용해서 열심히 풀어나가면 되는 문제이고, SSR대신 CSR로 해서 내가 익숙한 Vue보다는 React with TS로 화면을 구성해보려고 한다.그리고 테스트코드도 좀 더 다양한 방법으로 도전해보면 어떨까싶다(TODO).예를 들면 TestContainer라던지.. 아니면 MockMVC대신에 RestAssured(또는 RestAssuredMockMvc), WebTestClient등을 사용해볼 수도 있겠고 RestDocs 또는 RestDocs와 Swagger를 함께 사용하는 restdocs-api-spec를 써볼수도 있을 것 같다.일단 큰 목표는 마음과 머릿속 한켠에 자리를 잡아두고 MVP(Minimum Viable Product)라고 불리는 최소 기능 제품을 만들어서 정상작동하는데 문제가 없는 제품을 만들고 고도화를 통해서 기능개선 또는 기능추가, 사용성개선 등을 생각해보는게 맞지 않나 싶다!

백엔드인프런워밍업클럽3기프로젝트KotlinSpringboot스프링

lch9502

워밍업 클럽 3기 BE - 발자국 3주 차

1. 들어가며테스트 코드 강의는 미리 봤었고, 그 때 굉장히 오래 걸렸던 것 같네요. 아마 커리큘럼대로 따라가려면 굉장히 힘들었을 것 같아요...이번 주차 내용은 저한테는 굉장히 도움이 많이 됐었던 내용이였습니다.실제로 참고해서 회사 프로젝트에 테스트 코드를 적용하기도 했었구요.테스트 코드에 대한 막연한 두려움을 없애준 강의라서 정말 듣기 잘했다고 생각한 강의였습니다.  2. 학습했던 내용 나만의 키워드로 작성하기섹션 6. Spring & JPA 기반 테스트Layered Architecture관심사의 분리 때문에 레이어를 분리단위 테스트 VS. 통합 테스트여러 객체가 협력해서 어떤 하나의 기능을 동작한다면 통합테스트가 필요함단위 테스트만으로 커버하기 어려운 영역들이 생기기 때문IoC, DI, AOPORM, 패러다임의 불일치, HibernateSpring Data JPA@SpringBootTest VS. @DataJpaTest VS. @WebMvcTest@SpringBootTest스프링에서 통합 테스트를 위해 제공하는 에노테이션@SpringBootTest 에는 트랜잭션이 달려있지 않음 -> 더 선호@DataJpaTestjpa 관련된 빈들만 주입해서 서버를 띄워주기 때문@WebMvcTest컨트롤러 레이어만 딱 떼서 테스트를 하기 위해 컨트롤러 관련 빈들만 올릴 수 있는 가벼운 테스트 어노테이션@Controller와 @ControllerAdvice 등과 같은 빈만 주입 됨 @Transactional (readOnly = true)읽기 전용으로 하면 CRUD 작업 중에 CUD 작업이 동작하지 않음CQRSCommand 와 Query 를 분리해서 서로 연관이 없게끔 하려는 것@RestControllerAdvice, @ExceptionHandler커스텀 예외를 사용해서 처리하는 것도 자주 쓰이는 방법Spring bean validation@NotNull, @NotEmpty, @NotBlank, ...컨트롤러에서는 최소한의 검증만 하고 도메인 레이어나 서비스 레이어에서 검증할 것들은 따로 처리해서 책임을 잘 분리하기MockMvc MockMvc 란 Mock(가짜) 객체를 사용해 스프링 MVC 동작을 재현할 수 있는 테스트 프레임워크ObjectMapper@MockBean스프링 컨테이너에 mockito 로 만든 Mock 객체를 넣어주는 역할예시로, ProductService 빈에다가 적용을 하면 ProductService Mock 객체를 대신 스프링 컨테이너에 넣어줌  3. 학습 회고너무나 바쁜 한 주를 보냈습니다. 정신이 없었네요.옛날에 정리한 내용과 인강을 2배속으로 다시 봤습니다. 분명 정리를 했는데 처음 듣는 것 같은 내용들이 있어서 당황하긴 했습니다 ㅎㅎ.. 역시 완전히 자신의 것으로 만드려면 수많은 반복이 필요한 것 같네요.이번 기회에 반복할 수 있어서 좋은 기회였다고 생각합니다.특히 반복하면서 옛날에 들을 때는 인지하지 못했던 실무적인 관점이 다시 보이더라구요? 굉장히 의미 있었던 것 같습니다. 4. 미션 회고미션 Day 11사실 이번 미션은 회고하기가 애매합니다.냉정하게 저 스스로에 대한 평가를 하자면 열심히 했다고 보기가 어렵기 때문이죠.스프링 기반이 아니라서 단순히 미션의 기준에 맞춰서 필요하다고 생각한 것만 처리했습니다. (사실 코틀린 스터디를 따로 시작했는데 여기에 에너지를 너무 쏟아서,,,)다음주 라이브 시간에 딴 분들의 코드 리뷰를 보면 깨달음과 반성을 동시에 할 것만 같네요.. 😅😅😅

백엔드워밍업클럽3기백엔드클린코드자바

suover

인프런 워밍업 클럽 스터디 3기 - 백엔드 클린 코드, 테스트 코드 3주차 발자국

Readable Code: 읽기 좋은 코드를 작성하는 사고법Practical Testing: 실용적인 테스트 가이드강의와 함께한 인프런 워밍업 클럽 스터디 3기 - 백엔드 클린 코드, 테스트 코드 (Java, Spring Boot)3주차 발자국 입니다.학습 내용 요약이번 주에는 Spring & JPA 환경에 계층형 아키텍처(Layered Architecture) 및 테스트 기법(통합 테스트, MockMvc, JPA 테스트 등)을 학습했습니다.1⃣ Spring & JPASpring이 제공하는 IoC/DI 개념과 ORM(JPA, Hibernate) 기반 데이터 접근 방식을 이해했습니다.엔티티(Entity) 설계를 통해 도메인 모델링을 하고, 리포지토리(Repository) 계층으로 데이터 CRUD를 담당하도록 분리했습니다. 2⃣ Layered ArchitecturePresentation Layer(Controller)Business Layer(Service)Persistence Layer(Repository)컨트롤러는 요청/응답을 처리하고, 비즈니스 로직은 서비스가 담당하며, 데이터 접근은 리포지토리를 통해 수행합니다.3⃣ 테스트 코드 작성단위 테스트: 특정 클래스나 메서드 단위 검증. 예: JUnit + AssertJ 사용통합 테스트: 스프링 컨텍스트를 로드해 여러 레이어(Controller, Service, Repository)의 협력을 검증.MockMvc를 사용해 테스트하는 방식을 배웠습니다.4⃣ 유효성 검증(Bean Validation)DTO에서 @NotEmpty, @NotNull, @Positive 등 애노테이션을 사용하여 입력값을 검증하고,RestControllerAdvice로 BindException 등을 처리하여 예외 상황 시 API 응답을 표준화했습니다.✨ 학습 회고Spring & JPA 학습을 통해, 계층형 아키텍처 전체 흐름을 이해해야 함을 느꼈습니다.테스트 코드에서, 단위 테스트와 통합 테스트의 경계와 필요성을 체감했습니다.JUnit5 + AssertJ로 간단한 메서드 단위 검증을 할 때는 편리했지만, MockMvc로 검증하거나, @DataJpaTest로 테스트할 때는 더 많은 설정이 필요했습니다. 그만큼 테스트 커버리지가 넓어지며 신뢰도가 높아진다는 장점을 느꼈습니다.Validation과 ExceptionHandler를 통해 잘못된 입력을 막고, 표준화된 에러 응답을 제공하는 프로세스가 얼마나 중요한지 깨달았습니다.미션 해결 과정🎯 스터디카페 이용권 선택 시스템 테스트 미션미션스터디카페 이용권 선택 시스템의 테스트 코드를 작성하는 미션.미션 목표메시지 출력 테스트할인 로직 테스트사물함 선택 테스트 해결 과정OutputHandlerTest환영 메시지 + 공지사항 “프리미엄 스터디카페” 등의 문구가 제대로 찍히는지 확인.주문 내역 요약에 “이용 내역”, “이용권”, “사물함”, “총 결제 금액” 같은 키워드가 포함되는지 검증.StudyCafePassOrderTest할인율 및 가격 계산이 의도대로 동작하는지 (예: (250,000 + 10,000) - 할인액 25,000 = 235,000) 확인.사물함이 있는지 없는지에 따라 Optional<LockerPass>가 올바르게 반환되는지 테스트.StudyCafeSeatPassTest할인율이 0일 때 할인 금액이 0원인지, 0.1이면 정상 할인액이 계산되는지.시간권(HOURLY)은 cannotUseLocker() == true, 고정석(FIXED)은 false인지 등을 테스트로 보장. 회고도메인 로직이 잘 분리되어 있어 테스트 작성이 수월했습니다. 만약 복잡한 코드가 여기저기 있었다면, 테스트도 훨씬 어렵고 중복될 뻔했습니다. 또한 예외 처리 부분에 대한 테스트 코드의 필요성도 느껴졌습니다. 예외 상황에 대한 흐름을 명확하게 검증하고, 안정적인 동작을 보장하기 위해서는 예외 처리에 대한 테스트도 함께 구성되어야 한다고 생각했습니다.🔗 테스트 미션 깃허브 링크회고스스로 칭찬하고 싶은 점도메인 흐름 파악: Controller → Service → Repository 순으로 데이터가 흐르는 구조를 더욱 제대로 이해하게 되었습니다. 다양한 스프링 테스트 어노테이션(WebMvcTest, SpringBootTest, DataJpaTest 등)을 직접 적용하며, 각 용도에 맞게 테스트를 구분해본 경험이 유익했습니다.아쉬웠던 점 & 보완할 점테스트 격리: 통합 테스트 시 상태를 초기화하는 과정이 번거로웠고, 테스트 순서에 따라 의존성이 생기지 않도록 더 철저히 관리해야 함을 느꼈습니다.테스트 시나리오 다양성 부족: 대부분 해피 케이스 위주로 검증한 감이 있어, 더욱 폭넓은 예외 상황에 대한 시나리오를 추가 작성하면 안정성이 높아질 것 같습니다.다음 주 학습 목표Mock과 Test Double: Mock과Test Double 개념을 학습하고, 적절히 사용해 볼 예정입니다.테스트 환경 독립성:테스트 시 독립적인 환경을 유지하는 방법을 살펴볼 예정입니다.테스트 개선 기법:조금 더 간결하고 가독성 높은 테스트 작성을 시도해 볼 계획입니다.이번 주에는 Spring & JPA 환경에서의 테스트 코드 작성을 중점적으로 경험 했습니다. 이를 통해 서비스가 단계적으로 확장되더라도, 테스트 코드가 안정적인 작동을 보장할 수 있다는 점을 다시금 깨달았습니다. 앞으로도 테스트 우선 방식으로 새로운 기능이나 리팩토링을 진행해, 안전하고 효율적인 협업 환경을 유지해 나가겠습니다.감사합니다!

백엔드인프런워밍업클럽스터디백엔드클린테스트코드발자국회고3기

천준민

워밍업 클럽 3기 BE 클린코드&테스트 - 3주차 발자국

📖 강의 요약통합 테스트여러 모듈이 협력하는 기능을 통합적으로 검증하는 테스트일반적으로 작은 범위의 단위 테스트만으로는 기능 전체의 신뢰성을 보장 할 수 없다,풍부한 단위테스트 & 큰 기능 단위를 검증하는 통합 테스트 ORM객체 지향 패러다임과 관계형 DB 패러다임의 불일치ORM을 사용함으로써 개발자는 단순 작업을 줄이고, 비즈니스 로직에 집중할 수 있다.  Persistence LayerData Access의 역할비즈니스 가공 로직이 포함 되어서는 안된다. Data에 대한 CRUD에만 집중한 레이어 Business Layer비즈니스 로직을 구현하는 역할Persistence Layer와의 상호 작용 (Data를 읽고 쓰는 행위)을 통해 비즈니스 로직을 전개 시킨다.트랜잭션을 보장해야 한다. Presentation Layer외부 세게의 요청을 가장 먼저 받는 계층파라미터에 대한 최소한의 검증 !! CQRS (Command and Query Responsibility Segregation)데이터 저장소로 부터 읽기와 업데이트 작업을 분리하는 패턴= Command와 Query 를 분리 하기💡 미션 [Readable Code] 강의의 두 프로젝트(지뢰찾기, 스터디카페) 중 하나를 골라, 단위 테스트를 작성해 봅시다.미션 제출 url : https://github.com/2unmini/readable-code/tree/mission/day11 최대한 테스트 코드를 작성할때 현재 코드를 건드리지 않고 짤려고 했다 하지만 final Scanner 부분에서 문제가 발생했다 . 현재 코드는 각각의 테스트는돌아가지만 전체 테스트를 할때는 통과하지 못하지만 Scanner를 상수로 두지 않고 기존 코드를 변경한다면 테스트 코드도 성공적으로 잘 돌아갈 것이다. 💬 회고 👍 테스트에 관한 옛날에 대한 나의 생각나에게 테스트는 란 1+1 =2 처럼 이미 결과가 도출 되어있는 상황인데 테스트 코드를 작성 할 필요가 있을 까? 라는 의문 투성이 밖에 없었다. 정답은 아닐지는 모르지만 강의와 스터디를 통해 왜 테스트 코드를 작성해야 하는가를 어느 정도 이해를 하게 되는 것 같았다. 나만 1+1 =2 라고는 생각해도 이걸 뒤 받침해 줄 근거 및 신뢰성을 높이려면 테스트 코드가 필요해야 겠다.📎출처Practical Testing: 실용적인 테스트 가이https://www.inflearn.com/course/practical-testing-%EC%8B%A4%EC%9A%A9%EC%A0%81%EC%9D%B8-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B0%80%EC%9D%B4%EB%93%9C/dashboard

백엔드워밍업클럽3기클린코드테스트코드

치현

[인프런 워밍업 스터디 클럽 3기 풀스택] 3주차 발자국

학습 내용인프런 워밍업 클럽 스터디 3주차로,이번 주는 넷플릭스 프로젝트를 다루는 시간이었다.useInfiniteQuery와 Jotai(recoil 대체 전역상태 라이브러리)를 사용해볼 수 있었다.미션 3 구현 내용과제 구현 저장소Netflix 중 찜하기 관련 기능 포인트 1: favorites 테이블 추가actions/favoriteActions.ts"use server"; import { createServerSupabaseClient, handleError, PostgrestError, } from "@next-inflearn/supabase"; // Movie 타입 정의 export type Movie = { id: number; image_url: string; overview: string; popularity: number; release_date: string; title: string; vote_average: number; // Movie 타입에 favorites 필드 추가 favorites?: { // optional field로 추가 id: number; } | null; }; // SearchMoviesResponse 타입 정의 export type SearchMoviesResponse = { data: Movie[]; page: number; pageSize: number; hasNextPage: boolean; }; // 에러 케이스를 위한 타입 정의 type SearchMoviesError = { data: never[]; count: number; page: null; pageSize: null; error: PostgrestError; }; // 성공 케이스를 위한 타입 정의 type SearchMoviesSuccess = { data: Movie[]; page: number; pageSize: number; hasNextPage: boolean; }; export async function searchMovies({ search, page, pageSize, }: { search: string; page: number; pageSize: number; }): Promise<SearchMoviesSuccess> { const supabase = await createServerSupabaseClient(); // 현재 사용자 정보 가져오기 const { data: { user }, } = await supabase.auth.getUser(); // 쿼리 설정 const query = supabase .from("movie") .select( user ? ` *, favorites!left ( id ) ` : "*", // 로그인하지 않은 경우 favorites 정보를 가져오지 않음 { count: "exact" } ) .ilike("title", `%${search}%`) .order("popularity", { ascending: false }); // 로그인한 경우 현재 사용자의 즐겨찾기만 조회하도록 필터링 if (user) { query.eq("favorites.user_id", user.id); } const { data, count, error } = await query.range( (page - 1) * pageSize, page * pageSize - 1 ); const hasNextPage = count ? count > page * pageSize : false; if (error) { return { data: [], page, pageSize, hasNextPage: false, }; } // 반환된 데이터를 Movie 타입에 맞게 변환 const moviesWithFavorites = (data || []).map((movie: any) => ({ ...movie, favorites: movie.favorites?.[0] || null, // 즐겨찾기 정보 포함 })); return { data: moviesWithFavorites, page, pageSize, hasNextPage, }; } export async function getMovie(id: string) { const supabase = await createServerSupabaseClient(); const { data, error } = await supabase .from("movie") .select("*") .eq("id", id) .maybeSingle(); handleError(error); return data; } 포인트 2: profiles 테이블 추가utils/AuthProvider.tsx"use client"; import { useEffect } from "react"; import { createBrowserSupabaseClient } from "@next-inflearn/supabase"; import { useSetAtom } from "jotai"; import { userAtom } from "@/utils/jotai/atoms"; export function AuthProvider({ children }: { children: React.ReactNode }) { const setUser = useSetAtom(userAtom); const supabase = createBrowserSupabaseClient(); useEffect(() => { // 현재 세션 확인 supabase.auth.getSession().then(({ data: { session } }) => { setUser(session?.user ?? null); }); // Auth 상태 변경 구독 const { data: { subscription }, } = supabase.auth.onAuthStateChange((_event, session) => { setUser(session?.user ?? null); }); return () => subscription.unsubscribe(); }, [supabase, setUser]); return children; } 포인트 3: movies / favorites / profiles 테이블 연결favorites 테이블 내 profiles 테이블 내 회고 시간이 부족하다는 핑계로,더 디벨롭할 수 있는 부분이 많음에도 불구하고 생각했던 것들을 다 구현하진 못했던 것 같다.다만, 이렇게 틀을 갖춰놧으니 추후에 추가적인 기능을 정의해서 구현해보기 너무 좋을 것 같다.  

풀스택풀스택인프런워밍업스터디클럽Next3기SupabaseReact프론트엔드3주차발자국

바다다다

[인프런 워밍업 스터디 클럽 3기 FE] 3주차 발자국

수강강의1: 따라하며 배우는 자바스크립트 A-Z수강강의2: 따라하며 배우는 리액트 A-Z[19버전 반영]Tip: 워밍업 스터디 클럽을 참여하게 되면 [할인쿠폰]으로 강의를 할인 된 가격에 수강할 수 있다! 3주차 강의 수강3주차를 되돌아 보며..이번주 강의는 전반적으로 React와 친해지는(?) 시간을 가지게 된거 같다.React로 실제 화면을 구현해보고 각종 함수와 툴들을 사용해서 개발의 편의성을 높이는 것을 체감할 수 있었다.특히 주어진 미션과제를 수행하면서 React의 동작성, 상태관리에 대해서 더 깊게 알 수 있을 시간이 되었다.사실 이번주는 강의를 많이 듣지 못했다ㅠ 현업과 병행하다 보니 퇴근하고 강의 듣는다는 게 쉬운 일은 아니다. 특히 이번주는 시험검증 기간이라 이슈가 조금 많이 발생해서 야근도 잦고 그랬다... (모든 직장인, 학생 분들 화이팅입니다ㅠ 열심히 살아가봐요!) 그리고... 미션과제도 밀려서 주말에 몰아서 구현을 했다.미션과제를 수행하면서 중간중간 필요한 지식들은 강의를 들으면서 기록하며 학습을 진행했다.Javascript 강의 때와는 다르게 React 는 실습위주 이다보니 강의 내용을 정리한게 많지는 않다. (강의보면서 코딩따라 해보며 기능을 확인하고 미션에 적용하려고 했다)강의 내용을 다시 정리해보며..(주로 개인적으로 새롭게 알게된 것, 다시 기억해야하는 것들 위주로 정리했다)리액트는 프레임워크가 아니라 라이브러리React는 라이브러리, Vue, Angular는 프레임워크왜 라이브러리? 리액트는 전적으로 UI를 렌더링하는 것에 관여하기 때문상태관리, 라우팅, 테스트 등등을 위해 다른 라이브러리가 추가적으로 필요함 (vue, angular는 이런 것들이 이미 포함되어 있음)프레임워크: 어떠한 앱을 만들기위해 필요한 대부분의 것을 가지고 있는 것라이브러리: 어떠한 특정 기능을 모듈화 해놓은 것프레임워크는 라이브러리의 집합리액트 컴포넌트 - Component리액트 앱을 이루는 최소한의 단위, 여러 컴포넌트를 조합하여 하나의 페이지가 완성되는 것클래스형 컴포너트 - class component함수형 컴포넌트 - funcional component브라우저가 그려지는 원리와 가상돔리액트의 주요 특징 중 하나가 가상돔을 사용하는 것이다웹 페이지 빌드 과정 (CRP) - Critical Render Pathbrowser가 서버에서 페이지에 대한 HTML문서를 응답으로 받고, 해당 문서를 읽는다. 그 후 스타일을 입히고 뷰포트에 표시하게 된다DOM tree생성 → Render Tree생성(화면에 표시되는 모든 노드의 콘텐츠 및 스타일 정보를 포함) → Layout(reflow) → Paint(화면에 그리기)화면에서 DOM에 변화가 생기면 Render Tree부터 다시 랜더링 해야한다는 문제점!! → 비효율적→ 가상돔: 실제 DOM을 메모리에 복사해준 것데이터가 바뀌면 가상돔에 랜더링되고 이전에 생긴 가상돔과 비교해서 바뀐 부분만 실제 돔에 적용 시킴. 바뀐 부분을 찾는 과정을 diffing이라고 부르고, 바뀐 부분만 실제 돔에 적용시켜주는 것을 재조정(reconciliation)이라 한다.리액트 구조 살펴보기이름이 수정되면 안되는 파일public/index.html → page템플릿src/index.js → 자바스크립트의 시작점SPA는 어떻게 가능하게 되나?HTML5에서 History API를 사용해서 가능하게 함. (실제 react-dom에서 사용하는 방식)JSXJavascript syntax extension자바스크립트의 확장 문법이다React에서 의무적으로 사용하는 것 XJSX는 babel을 통해 변환된다컴포넌트에 여러 엘리먼트 요소가 있다면 반드시 부모요소 하나로 감싸줘야한다.JSX Keykey는 리액트가 변경, 추가 또는 제거된 항목을 식별하는데 도움이 된다. 요소에 안정적인 ID를 부여하려면 배열 내부의 요소에 키를 제공해야 한다 (key값을 기준으로 바뀐 가상돔을 감지한다)key에는 unique한 값을 넣어주자. index는 비추!React State컴포넌트의 렌더링 결과물에 영향을 주는 데이터를 갖고있는 객체이다. state가 변경되면 컴포넌트는 리랜더링(re-rendering)된다. 또한 state는 컴포넌트 안에서 관리된다.React HooksReactConf 2018에서 발표된 class없이 state를 사용할 수 있는 새로운 기능리액트의 생명주기react hooks를 통해 함수형 컴포넌트에서도 생명주기를 사용할 수 있게 데이터를 가져와서 컴포넌트 시작하자 마자 API호출하고 많은 부분을 사용할 수 있게되었다→ 코드가 간결해짐 (useEffect를 통해 componentDidMount, componentDidUpdate, componentWillUnmount를 다 수행해줌)HOC(higher order component)를 Custom react hooks로 대체해서 너무 많은 wrapper컴포넌트를 줄이게 된다.→ HOC: 화면에서 재사용 가능한 로직만을 분리해서 component를 만들고, 재사용 불가능한 UI와 같은 다른 부분들은 parameter로 받아서 처리하는 방식 (wrapper가 많아지면 데이터의 흐름을 파악하기 어려워짐)State, Propsstate해당 컴포넌트 내부에서 데이터를 전달할 때state는 mutable 변경가능하다state 이 변하면 re-render된다props상속하는 부모컴포넌트에서 자식 컴포넌트로 전달한다읽기전용으로 작년 컴포넌트 입장에서는 변하지 않는다. (변하게 하고자 하면 부모 컴포넌트에서 state를 변겨해줘야한다)구조 분해 할당(Destructuring)배열이나 객체의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게 하는 JS표현식클린코드를 위해 객체, 배열에 대해 구조 분해 할당Redux상태 관리 라이브러리redux데이터는 하나의 방향으로만 흐른다flow: react component에서 어떠한 이벤트가 발생하면 → Action 객체(어떤 작업을 수행할 것인지 description을 담고 있음)를 통해 reducer함수에게 액션을 발생하라고 한다.(dispatch) → reducer함수에서 로직 처리 → store내부 상태 업데이트: redux store state update → 새롭게 업데이트 된 상태를 이용해서 다시 렌더링 → component re-renderingRedux Tookitredux로직을 작성하기 위한 공식 권장 접근 방식 강의 내용을 들으면서 아쉬웠던 점..앞서 언급한 대로 이번주에는 강의보다는 실습 과제에 조금 더 초점을 두었다. 미션을 수행하면서 모르는 부분을 강의에 가서 찾아 듣고 다시 구현하고 하는 식으로 학습을 진행했다.React는 이론 보다는 실제 사용하고 구현해보는게 더 빠르게 성장하는거 같다. 특히 상태관리, 페이지 라우터 등 강의에서도 알려주시지만, 미션과제를 수행하면서 직접 기능을 동작시킬 수 있게 구현하는게 더 깊이 이해할 수 있었던 거 같다.아쉬운점은ㅠ 강의를 많이 못 들었다는거? 수료식까지 아직 시간이 조금 남았으니까 남은 강의도 열심히 들어보겠다.3주차 미션 완료React미션 두 개(포켓몬, 쇼핑몰)를 토요일 벼락치기로 수행했다.간단하다고 생각했는데 React가 아직 익숙하지 않아 중간중간 오류가 많이 발생했었고, 특히 프론트엔드는 화면이 바로 눈에 보이다 보니까 CSS 스타일 적으로 내맘대로 적용되지 않아서 화가났다(?). 그래도 tailwindcss에서 만들어진 component, icon들을 활용해서 미션을 수행완료할 수 있었다.React - Mission3: 포켓몬 도감 앱 만들기Demo: https://pokemon-mission-3.netlify.app/Source Code: https://github.com/rim0703/React-study/tree/react/mission3/React/3-pokemonpokeAPI 라는 오픈소스 API를 제공하는 곳이 있어서 외부API를 호출해서 구현했다API문서가 얼마나 중요한 역할을 하는지 이번 미션에서 깊이 깨달았다.. ㄷㄷ(백엔드 화이팅)(지연로딩 적용) 사용자의 화면 로딩 속도를 고려하면 첫 화면에 20개씩 불러오도록 했고, 스크롤이 하단에 도달하면 추가로 호출하도록 구현했다.(Trick) 시연화면에서 로딩이 출력되는데 사실 화면이 너무 빨리 렌더링 완료돼서 로딩이 보이지 않아 로딩 동글뱅이를 보기 위해 setTimeout으로 1초 지연을 넣었다 ^^;;(검색창) 조금 버그가 있다. 페이지를 영문이 아닌 한글로 만들다보니 검색도 한글로 하도록 했는데 상태를 상위-하위 컴포넌트 간에서 전달하다 보니 조금 어려웠다. 또한 API문서 기본은 영문으로 제공해서 영문과 한글을 매칭시키는 별도의 작업도 필요했다. redux를 사용해서 검색창을 상단 navigation바에 위치해보려고 한다.  React - Mission6: 리덕스를 이용한 쇼핑몰(버거몰) 앱 만들기Demo: https://burger-mall-mission-6.netlify.app/Source Code: https://github.com/rim0703/React-study/tree/react/mission6/React/6-shopping-mall1주차 미션 내용에서 언급했듯이.. JS강의에서 만들었던 메뉴 화면을 그대로 따와서(CSS공부가 아니니까^^) 쇼핑몰과 비슷한 기능들이 있는 버거몰을 만들어보았다.(컴포넌트화) HTML,JS,CSS로 구현된 내용을 React로 옮겼고, 원래 파일 1개씩 정의된 내용을 React Component로 분류하였다.(장바구니-localstorage) DB를 별도로 연동된게 아니여서 localstorage를 활용해서 장바구니를 구현하였다.(Redux) 리덕스 강의 내용에 기반한 프로젝트이지만 Redux강의를 듣고 이해가 잘되지 않아서 우선 기본 방식으로 앱을 구현하였다. Redux공부를 조금 더 하고 리팩토링을 진행할 예정이다. 마지막으로 3주차 회고이제 수료까지 얼마남지 않았는뎅,, 남은 시간동안 강의를 열심히 들어야겠다.너무 빡빡한 일정이었지만 3주차까지 버텨오고 React에 대해 친해질 수 있는 시간이되어서 뿌듯하다.현업에서 Vue를 사용하다 기술 전환을 위해 React를 도입한다고 해서 급하게 스터디에 신청해서 힘들지만 조금만 더 버티면되지 않을까? 직장과 병행하니.. 이게 쉬운일은 아닌거 같다. 모든 직장인들 화이팅입니다!흑흑.. 미션 기능 중에 동작하지 않는게 있어서 수정하고 다시 배포하고 이제 자러간당ㅠㅠ다음주는 더 성장한 내가 되어 있기를!! 모두 완주까지 화이팅입니다~(2025.03.23 새벽 03:26)<끝>

프론트엔드워밍업스터디3기프론트엔드미션JSJavascriptReactRedux회고

leeebug

워밍업 클럽 스터디 3기 FS - 3주차 발자국

워밍업 클럽도 벌써 3주차!처음 스터디를 시작할 때와 비교하면 가장 큰 소득은 React Query에 익숙해졌다는 것 그리고 무언가에 몰입하면서 성취감을 느꼈다는 것이다.단순히 기능을 구현하는 걸 넘어서 최적화나 에러 핸들링까지 고민하는 과정이 꽤 재밌었다.이번주에는 인피니트 쿼리와 추가 기능으로 좋아요 기능을 구현해야해서 지난주와 마찬가지로 조금 일찍 학습을 시작했다.깃 레포는 역시 첫번째 과제에 사용했던 템플릿을 거의 수정없이 그대로 사용해서 역시나 개발환경 구축은 무리없이 진행했다.다만, 한 가지 아쉬운 점이라면 터보레포 같은 모노레포 도구를 도입했어야 하지 않았나 하는 생각이 들었다는 점이다.현재 방식은 각 주차별 과제를 독립적인 레포로 관리하고 있는데, 공통 유틸이나 자주 사용하는 설정 파일을 계속 복붙하는 과정에서 의외로 피로감을 느꼈다.우선 마지막 과제까진 지금의 방식을 유지하고 스터디 마무리 이후에 터보 레포에 대해서는 개별적으로 학습을 해볼 예정이다.📝 3주차 학습useInfiniteQuery무한 스크롤 및 페이지네이션을 위한 React Query 훅fetchNextPage를 사용해 추가 데이터 요청getNextPageParams로 다음 페이지 여부 관리 useInViewreact-intersection-observer 라이브러리의 훅특정 요소가 화면에 보이는지 감지(뷰포트 진입 여부로 확인)무한 스크롤 구현 시 useInfiniteQuery와 함께 사용threshold, rootMargin으로 감지 범위 조절 가능📋 3주차 미션💬 GitHub 저장소🚀 데모 영상 보러가기미션 해결 과정 요약이번주 미션의 필수 구현 과제는 무한 스크롤과 SEO 추가, 영화 검색 기능 구현하기였다. 추가 기능으로 영화 좋아요 기능을 구현했는데, 예전에 SNS를 만들때 경험해봤던 기능이라 쉽게 구현할 수 있을 것이라 기대했다. 하지만 예상과는 달리, 유저 식별 기능이 없다는 점이 문제였다. SNS 좋아요 기능 구현 당시에는 사용자 ID 기반으로 좋아요를 관리했지만 이번 프로젝트는 익명 유저 환경이라 데이터를 어떻게 저장하고 관리할지 고민이 필요했다.처음에는 movies, users, liked_movies 3개의 테이블을 생성하여 user_id, movies_id를 복합키로 설정해 브라우저별 익명 유저를 관리하는 방식을 시도했으나 구현 복잡도가 너무 높아지는 문제로 단순화하는 방식으로 변경했다.movies 단일 테이블에 like_count 필드를 추가하고 브라우저별로 좋아요 상태를 관리하는 방식으로 해당 기능을 구현했다. 이 방식의 단점은 브라우저 변경 시 개인별 좋아요 리스트를 추적할 수 없다는 점이지만 애초에 유저 식별 기능을 배제한 상황에서 선택할 수 있는 최적의 방식이라고 판단하여 적용했다.그리고 강의에서는 movies.id를 auto increment id로 구현했지만 더 나은 확장성을 위해서 uuid를 고려했다. 다만 uuid는 URL에서 사용하기 불편하여 가독성이 좋은 slug 칼럼을 별도로 추가하였다. API 요청 파라미터를 id에서 slug로 대체하면서 가독성과 SEO 최적화까지 함께 챙겨갈 수 있었다.slug Column 추가ALTER TABLE myreel_movies ADD COLUMN slug TEXT UNIQUE;중복되는 Row 제거 (제공되는 DB에 중복되는 데이터가 9건 발견되었다.)DELETE FROM myreel_movies WHERE id NOT IN ( SELECT id FROM ( SELECT id, title, order_index, ROW_NUMBER() OVER (PARTITION BY title ORDER BY order_index ASC) AS row_num FROM myreel_movies ) ranked WHERE row_num = 1 );영화 title 기준으로 slug 생성예시 - 'Dune: Part Two' -> 'dune-part-two'UPDATE movies SET slug = LOWER(REGEXP_REPLACE(title, '[^a-zA-Z0-9]+', '-', 'g')) WHERE slug IS NULL;과제 추가 구현 기능✅ 영화 좋아요 추가api/movies/:slug/likeconst likeMovie = async () => { try { const res = await fetch(`${baseUrl}${API_ENDPOINTS.LIKE(slug)}`, { method: 'POST', }) if (!res.ok) { throw new Error(CLIENT_ERROR.MOVIE_LIKE_FAILED.message) } const data: LikeMovieResponseDTO = await res.json() setLikeCount(data.like_count) // 서버에서 받아온 새로운 좋아요 수로 업데이트 setIsLiked(true) // 로컬 스토리지에 영화 추가 또는 업데이트 const likedMovies: LikedMovie[] = JSON.parse(localStorage.getItem('likedMovies') || '[]') // 이미 좋아요를 누른 영화가 있다면, likeCount를 업데이트 const existingMovieIndex = likedMovies.findIndex((movie) => movie.slug === slug) if (existingMovieIndex >= 0) { likedMovies[existingMovieIndex].likeCount = data.like_count // 좋아요 수 업데이트 } else { // 좋아요를 누른 적이 없다면 새로 추가 const newLikedMovie = { slug, likeCount: data.like_count } likedMovies.push(newLikedMovie) } localStorage.setItem('likedMovies', JSON.stringify(likedMovies)) } catch (error) { console.error(error) } }✅ 영화 좋아요 삭제api/movies/:slug/unlikeconst unlikeMovie = async () => { try { const res = await fetch(`${baseUrl}${API_ENDPOINTS.UNLIKE(slug)}`, { method: 'POST', }) if (!res.ok) { throw new Error(CLIENT_ERROR.MOVIE_UNLIKE_FAILED.message) } const data: LikeMovieResponseDTO = await res.json() setLikeCount(data.like_count) setIsLiked(false) // 로컬 스토리지에서 해당 영화 정보 삭제 const likedMovies: LikedMovie[] = JSON.parse(localStorage.getItem('likedMovies') || '[]') const updatedLikedMovies = likedMovies.filter((movie) => movie.slug !== slug) // 로컬 스토리지 갱신 localStorage.setItem('likedMovies', JSON.stringify(updatedLikedMovies)) } catch (error) { console.error(error) } }개인 챌린지 기능✅ 메인 페이지 최상단으로 가는 버튼 추가메인 페이지에서 500px 이상 스크롤 내릴 경우 최상단으로 이동하는 버튼 생성behavior: 'smooth' 로 부드럽게 이동 ✅ 검색 결과 없을 경우, 좋아요 많은 순 추천 영화 6개 노출되는 기능 구현좋아요가 많은 영화 외에도 최근 개봉한 영화 같은 다양한 리스트 제공 예정api/movies/most-liked👀 3주차 회고지난주에 적용했던 매니져 컴포넌트 / UI 컴포넌트로 분리하는 방식이 Container-Presentational Component 패턴 과 유사한 방식이라는 것을 다른 러너분의 발자국을 통해 알게되었다. 궁금해서 조금 더 찾아보니, 이 패턴은 과거 클래스형 컴포넌트 시절에는 HOC(High Order Component)와 함께 많이 사용되었지만, 함수형 컴포넌트에서도 여전히 유효한 방식이라는 것을 알게되었다. 이번주에는 기존 패턴을 유지하면서도, 비즈니스 로직을 최대한 커스텀 훅으로 분리하는 연습을 진행했다. 이를 통해 컴포넌트의 역할을 더욱 명확하게 나누고, 재사용성과 유지보수성을 높이는 방향으로 조금씩 개선되고 있다는 것을 체감했다.👻 배포 관련 이슈 (3월 22일 추가)4주차에 스터디 기간 개발한 4개의 프로젝트를 모두 배포하는것이 기존 스터디 일정이지만.. 시간적 여유가 생겨서 1~3주차 프로젝트를 미리 배포해봤다. vercel은 기존에 사용하던 툴이었는데 한번에 3개의 프로젝트를 배포하려고 시도하는 과정에서 수 많은 에러를 경험했다. 4주차 프로젝트 배포시, 추후 다른 프로젝트 배포시에 참고할 수 있도록 간단하게 정리해본다. ✅ @/components/... 앨리어스 관련 캐싱 이슈문제 개발 환경에서는 정상 작동하던 import가 Vercel 배포 시에만 Module not found 에러 발생원인Vercel의 캐싱 문제 또는 파일명 인식 관련 문제(대소문자, 내부 경로 변경 후 캐시 꼬임)해결@ 앨리어스 문제를 의심하여 상대 경로로 변경 후 재배포 시도 -> 해결 안됨컴포넌트 경로의 대소문자 확인후 재배포 시도 -> 해결 안됨 Title.tsx 파일명을 AppTitle.tsx로 변경하여 강제로 캐시 무력화 후 재배포 시도 -> 해결 ✅ params 비동기 처리 관련 타입 에러 (Next.js 15)문제page.tsx에서 params를 비동기적으로 처리하려 하자, params 타입이 Promise로 인식되어 타입 오류 발생원인 (깃헙 이슈 참고)Next.js 15 내부적으로 PageProps가 비동기적 처리를 기대하거나 타입 추론이 변경됨params 타입이 Promise<any>로 추론되어 관련 에러 발생PageParams 제네릭 타입 해석 충돌next dev에서는 정상 작동하지만 next build 시 오류 발생해결params의 인터페이스를 명시적 타이핑 -> 해결 안됨params의 타입 any로 명시하고 타입 단언으로 처리 -> 해결 안됨배포 시 안정성 확보를 위해서 Next.js 14 + React 18 버전으로 롤백 -> 해결✅ Tailwind CSS 적용 안됨문제배포된 페이지에서 Tailwindcss 클래스가 적용되지 않음원인Next.js 15 -> Next.js 14, React 19 -> React 18로 롤백하는 과정에서 관련된 의존성 충돌이 일어난것으로 예상됨해결Tailwindcss, postcss, autoprefixer 의존성 삭제 후 캐시 초기화 후 재설치 -> 해결 ✅ 환경 변수(NEXT_PUBLIC_BASE_URL) 미설정으로 fetch 실패문제빌드 시 fetch 요청이 localhost:3000으로 날아가면서 ECONNREFUSED 에러 발생원인Vercel 환경 변수 설정 시 NEXT_PUBLIC_BASE_URL 값을 localhost:3000으로 설정하여 에러 발생해결해당 환경 변수를 실제 배포 URL로 변경 후 재배포 시도 -> 해결

풀스택워밍업클럽3기회고발자국3주차

바다다다

[인프런 워밍업 스터디 클럽 3기 FE] 2주차 발자국

수강강의1: 따라하며 배우는 자바스크립트 A-Z수강강의2: 따라하며 배우는 리액트 A-Z[19버전 반영]Tip: 워밍업 스터디 클럽을 참여하게 되면 [할인쿠폰]으로 강의를 할인 된 가격에 수강할 수 있다!2주차 강의 수강2주차를 되돌아 보며..이번주에는 지난주 Javascript강의를 마무리하고 드디어 React에 대한 학습에 진입하게 되었다.Javascript후반부 강의에서는 주로 디자인 패턴, Iterator, Generator에 대해 알아가고 마지막으로 Todo app, spread sheet 등을 따라 만들어 보면서 Javascript문법에 대해 조금 더 익숙해지고 복습하는 시간을 가지었다.React는 초반에 설치와 프로젝트 환경설정을 시작으로 처음부터 앱을 따라 만들어 보면서 React의 구조에 대해 알아보았다. TODO앱과 Netflix따라 만들기 강의를 따라서 코딩을 진행을 하였지만 환경설정이나 알수없는 오류등이 발생해서 바로 미션 과제를 중점으로 학습을 진행하였다.강의에서 언급한 이론을 실제 미션 과제에서 실습으로 결과물을 얻을 수 있어서 뿌듯했다. 지난주 과제에 비해 난이도가 많이 높아져서 진도를 따라잡기에 많이 버거웠던것도 사실이다. (특히 직장인이라면...More and more...hard..)강의 내용을 다시 정리해보며..(주로 개인적으로 새롭게 알게된 것, 다시 기억해야하는 것들 위주로 정리했다)Generator Generator사용자의 요구에 따라 다른 시간 간격으로 여러 값을 반환할 수 있음일반 함수 → 한번 실행으로 끝까지 실행됨generator 함수 → 일시적으로 정지될 수도 있고, 다시 시작할 수도 있음function* sayNumbers() {...} 별표로 generator함수 생성yield 함수를 정지시킴 → 일반함수에서 return과 동일function* sayNumbers(){ yield 1; yield 2; yield 3; } const number = sayNumbers() console.log(number.next()) // {value:1, done:false} console.log(number.next()) // {value:2, done:false} console.log(number.next()) // {value:3, done:false} console.log(number.next()) // {value:undefined, done:true} lazy evaluation에 활용가능계산의 결과값이 필요할 때까지 계산을 늦춰서 필요한 데이터를 필요한 순간에 생성Singleton Pattern클래스의 인스턴스화를 하나의 객체로 제한하는 디자인 패턴. 시스템 전체에서 작업을 조정하는 데 정확히 하나의 객체가 필요한 경우에 유용하다클래스가 존재하지 않는 경우 클래스의 새 인스턴스를 생성하는 메서드로 클래스를 생성하여 구현할 수 있음. 인스턴스가 이미 존재하는 경우 해당 개체에 대한 참조를 반환⇒ 객체는 1개만 만들고 그것을 계속 재사용하는 것 (값이 한곳에서 바뀌면 전역으로 바뀜)Factory Pattern특수 함수인 팩토리 함수를 사용하여 비슷한 객체를 많이 만들 수 있음⇒ 비슷한 객체를 반복적으로 생성해야하는 경우 사용Mediator Pattern (중재자 패턴)객체 그룹에 대한 중앙 권한을 제공한다. (예: 채팅방)Observer Patternevent-driven시스템을 이용하는 것 (예: facebook알림)topic에 대해 register하고 publisher-subscriber구조를 가지게 됨Module Pattern코드를 더 작고 재사용 가능한 조각으로 분할하는 것을 도와준다js코드를 작성하고 export지시자를 변수나 함수 앞에 붙이면 외부 모듈에서 해당 변수나 함수에 접근가능하게 된다import로 외부 모듈을 가져올 수 있음모듈은 특수한 키워드나 기능과 함께 사용되므로 HTML에서 script태그를 넣을 때 type='module' 속성을 추가해줘야 한다항상 strict모드로 실행된다지연 실행 된다인라인 모듈 스크립트도 비동기 처리할 수 있다외부 orgin에서 스크립트를 불러오려면 CORS헤더가 있어야 한다중복된 스크립트는 무시한다 (최초 호출될 때 한번만 실행)구형브라우저에서는 <script nomodule> ... </scirpt> 로 module을 지원하지 않을 경우 예외처리를 할 수 있음리액트는 프레임워크가 아니라 라이브러리React는 라이브러리, Vue, Angular는 프레임워크왜 라이브러리? 리액트는 전적으로 UI를 렌더링하는 것에 관여하기 때문상태관리, 라우팅, 테스트 등등을 위해 다른 라이브러리가 추가적으로 필요함 (vue, angular는 이런 것들이 이미 포함되어 있음)프레임워크: 어떠한 앱을 만들기위해 필요한 대부분의 것을 가지고 있는 것라이브러리: 어떠한 특정 기능을 모듈화 해놓은 것프레임워크는 라이브러리의 집합리액트 컴포넌트 - Component리액트 앱을 이루는 최소한의 단위, 여러 컴포넌트를 조합하여 하나의 페이지가 완성되는 것클래스형 컴포너트 - class component함수형 컴포넌트 - funcional component브라우저가 그려지는 원리와 가상돔리액트의 주요 특징 중 하나가 가상돔을 사용하는 것이다웹 페이지 빌드 과정 (CRP) - Critical Render Pathbrowser가 서버에서 페이지에 대한 HTML문서를 응답으로 받고, 해당 문서를 읽는다. 그 후 스타일을 입히고 뷰포트에 표시하게 된다DOM tree생성 → Render Tree생성(화면에 표시되는 모든 노드의 콘텐츠 및 스타일 정보를 포함) → Layout(reflow) → Paint(화면에 그리기)화면에서 DOM에 변화가 생기면 Render Tree부터 다시 랜더링 해야한다는 문제점!! → 비효율적→ 가상돔: 실제 DOM을 메모리에 복사해준 것데이터가 바뀌면 가상돔에 랜더링되고 이전에 생긴 가상돔과 비교해서 바뀐 부분만 실제 돔에 적용 시킴. 바뀐 부분을 찾는 과정을 diffing이라고 부르고, 바뀐 부분만 실제 돔에 적용시켜주는 것을 재조정(reconciliation)이라 한다.  강의 내용을 들으면서 아쉬웠던 점.. 강의 내용에 대해서 불만족 스러운 부분은 없다! 너무 알차게 구현되어진 강의이고 다만 진도에 따라서 많은 양의 강의를 한꺼번에 들어야 하다보니 모두 소화하기에는 어려웠다.강의 외에 미션과제도 수행해야 하다 보니 시간적으로 지난주에 비해 많이 부족했다. 아래 미션과제에서도 볼 수 있겠지만, 지난주와 달리 기본적인 모든 기능을 구현하지는 못했다.(특히! 디즈니플러스 클론코딩ㅜ)2주차 미션 완료Javascript미션은 지난주에 이어서 그나마 완성하기 했으나,React미션은 초보자의 입장에서 강의내용 수준으로 따라가기에는 어려웠다.처음이다보니 모든게 낯설게 느껴졌고, 그래도 다른 강의 및 자료를 통해 많은 학습을 할 수 있었다.특히 컴포넌트를 나누는 기준과 React에서 컴포넌트를 조합해서 웹 페이지를 구성한다는 것을 미션 실습과제를 통해 좀 더 쉽게 깨달을 수 있었던 것 같다. Javascript - Mission6: 비밀번호 생성기Demo: https://rim0703.github.io/React-study/Javascript/6-pw-generator/Source Code: https://github.com/rim0703/React-study/tree/main/Javascript/6-pw-generator Javascript - Mission7: 타이핑 테스트Demo: https://rim0703.github.io/React-study/Javascript/7-typing-test/Source Code: https://github.com/rim0703/React-study/tree/main/Javascript/7-typing-test React - Mission1: 예산 계산기Demo: https://gentle-starburst-b5ca45.netlify.app/Source Code: https://github.com/rim0703/React-study/tree/react/mission1 React - Mission2: 디즈니 플러스 앱Demo: https://disney-plus-clone-mission2.netlify.app/Source Code: https://github.com/rim0703/React-study/tree/react/mission2참고자료: https://www.youtube.com/watch?v=3NHYl0Lo74A이건 할말이 많은데.. 사실 아래 데모처럼 메인화면만 완성했다. 맨땅에서 시작하기에는 너무 막막해보여서 유튜브에 클론코딩 영상이 있어서 따라 만들어보았다.추가로 메인 로그인 화면, 영화 상세 내용 팝업화면이 추가로 구현일 필요하다.(추후 시간이 있으면 보완할 예정!)마지막으로 2주차 회고이번 한주간은 너무 정신없이 지난거 같다. 강의, 미션과제 외에 처음으로 접하는 React 내용에 설정이나 오류 등에 대해 검색하면 이슈 attack을 하기 위해 많이 시간이 필요했다. 이런 부분은 점차 많이 연습해보면 적응하지 않을까 싶다.진도표에 따르면 이제 3일만 더 수강하면 강의내용과 미션은 끝나는 일정이다. 최대한 남은 시간에 React강의를 끝내고 미션 과제 구현에 집중할 계획이다. 단기간에 많은 시간과 노력을 쏟아올린 만큼 프론트엔드에서 React에 대한 부담감(?) 두려운 마음을 조금이나마 떨쳐보고자 한다.강의수강이 끝나면 지금까지 구현한 미션 과제에 대해 보완할 부분이 있는지, 그리고 지금까지 강의를 들으면서 노션에 정리해두었던 내용을 다시 한번 복습할 계획이다.마지막 남은 기간도 화이팅 해보자!!!<끝>

프론트엔드워밍업스터디3기프론트엔드2주차발자국JavascriptHTMLCSSReact

suover

인프런 워밍업 클럽 스터디 3기 - 백엔드 클린 코드, 테스트 코드 2주차 발자국

Readable Code: 읽기 좋은 코드를 작성하는 사고법Practical Testing: 실용적인 테스트 가이드강의와 함께한 인프런 워밍업 클럽 스터디 3기 - 백엔드 클린 코드, 테스트 코드 (Java, Spring Boot)2주차 발자국 입니다.학습 내용 요약이번 주는 클린 코드 작성법에 이어 코드 구조와 리팩토링, 그리고 테스트 코드 작성법을 본격적으로 학습했습니다.1⃣ 주석의 양면성주석은 코드 자체로 표현하기 어려운 의사결정의 히스토리를 기록할 때 사용해야 합니다.자주 변하는 정보는 주석으로 표현하면 안 되고, 주석은 코드와 함께 꾸준히 관리해야 합니다.부정확한 주석은 주석이 없는 코드보다 더 해롭습니다.2⃣ 변수와 메서드의 나열 순서변수는 사용하는 순서대로 나열하고, 공개 메서드를 먼저 배치하여 외부 세계에 제공하는 기능을 명확히 드러냅니다.비공개 메서드는 공개 메서드에서 호출되는 순서로 나열하여 코드 읽기 흐름을 자연스럽게 만듭니다.나열 순서도 의도를 표현하는 중요한 수단이 될 수 있음을 배웠습니다.3⃣ 패키지 나누기패키지 구조는 도메인 문맥을 반영하여 설계하며, 너무 잘게 나누거나 너무 크게 뭉치는 것을 지양해야 합니다.팀 내 합의가 중요하며 초기 설계 단계에서 패키지 구조를 충분히 고민해야 합니다.4⃣ 적정 기술의 사용모든 기술은 적정한 수준과 시점에서 활용해야 하며, 무리한 추상화나 오버 엔지니어링은 오히려 개발 효율성을 떨어뜨립니다.클린 코드는 만능 해결책이 아니며, 상황과 목적에 맞는 균형 잡힌 접근이 중요합니다.5⃣ 테스트 코드와 TDD자동화된 단위 테스트(JUnit5, AssertJ)를 통해 신뢰성과 유지보수성을 높이고, 빠른 피드백을 받을 수 있습니다.TDD(Test Driven Development)를 통한 선(先) 테스트 작성, 후(後) 기능 구현 방식을 이해했습니다.Given-When-Then 구조를 이용한 BDD(Behavior Driven Development) 스타일로 작성하면 도메인 로직을 명확히 표현할 수 있습니다.미션 해결 과정🎯 스터디카페 이용권 선택 시스템 리팩토링 미션미션'스터디 카페 이용권 선택 시스템'의 기존 시스템에 중복된 로직과 혼재된 책임을 명확히 분리하고, 객체지향 원칙을 준수하여 코드 구조를 개선하는 리팩토링 미션입니다.접근 관점추상화 레벨 맞추기: 중복 로직을 별도 메서드로 추출했습니다.객체의 책임 분리: StudyCafeOrder 객체를 추가해 할인 계산 로직을 전담했습니다.DIP(의존성 역전 원칙): 파일 접근 로직을 인터페이스(StudyCafeRepository)로 추상화하여 확장성을 확보했습니다.해결 과정1. 기존의 복잡한 분기 조건(if-else)을 메서드로 깔끔하게 정리했습니다.Before: if (HOURLY) { ... } else if (WEEKLY) { ... } else if (FIXED) { ... } 로직이 길고, 사물함 로직도 그 안에서 처리.After:public void run() { // 1) passType 선택 (시간권/주단위/고정석) StudyCafePassType passType = inputHandler.getPassTypeSelectingUserAction(); // 2) pass 선택 StudyCafePass selectedPass = selectPass(passType); // 3) 사물함 선택 (고정석 only) StudyCafeLockerPass lockerPass = maybeSelectLocker(selectedPass); // 4) 주문 객체 생성 & 결과 출력 StudyCafeOrder order = new StudyCafeOrder(selectedPass, lockerPass); outputHandler.showOrderResult(order); }이점: 각 단계가 메서드로 분리되어 이해하기 쉬움. passType별로 중복된 분기 로직이 크게 줄었음.      2. 할인 및 총금액 계산 로직을 전담하는 객체를 만들어 로직을 명확히 했습니다.Before: 할인금액, 총액 계산 로직이 OutputHandler.showPassOrderSummary 안에 위치.After: StudyCafeOrder라는 도메인 객체가 이 로직을 담당.public int getDiscountAmount() { return (int) (selectedPass.getPrice() * selectedPass.getDiscountRate()); } public int getTotalPrice() { int discountPrice = getDiscountAmount(); int lockerPrice = (lockerPass != null) ? lockerPass.getPrice() : 0; return selectedPass.getPrice() - discountPrice + lockerPrice; }이점: 필요 시 “할인 정책”이 바뀌어도 StudyCafeOrder만 수정하면 되며, 출력부는 깔끔하게 유지.   3. 파일 접근 방식을 인터페이스와 구현체로 나누어 유지보수와 확장성을 높였습니다.Before: StudyCafeFileHandler라는 클래스에서 직접 파일 접근 + 변환.After: StudyCafeRepository(추상) + StudyCafeFileRepository(구현).StudyCafePassMachine은 StudyCafeRepository repository에만 의존 → DIP 준수.향후 DBRepo나 InMemoryRepo 추가 시 StudyCafeRepository만 구현하면 됨. 회고리팩토링을 통해 코드 가독성과 유지보수성이 눈에 띄게 향상되었습니다. 특히 객체지향 원칙(SRP, DIP)을 철저히 적용한 결과, 각 객체가 단 하나의 명확한 책임을 가지면서, 향후 변경이 발생해도 최소한의 수정만으로 대응 가능해졌습니다. 규칙의 절대성이 아닌 "적정선"을 찾는 것이 매우 중요하다는 점을 깊이 깨달았습니다.🔗 리팩토링 미션 깃허브 링크회고스스로 칭찬하고 싶은 점학습한 내용을 즉시 코드에 적용하며 원칙을 체화하고자 노력한 점단순한 기능적 개선을 넘어, 구조적이고 근본적인 리팩토링을 고민한 점아쉬웠던 점 & 보완할 점코드 리뷰에서 코드 확장성과 유지보수성을 높이기 위한 설계가 부족했음을 느꼈습니다. 다음에는 코드 확장성과 유지보수성을 고려한 구조를 더욱 더 고민해 보겠습니다.지나치게 추상화에 치우치는 경향을 느꼈고, 다음에는 더 실용적인 균형을 유지하려 합니다.다음 주 학습 목표다음 주부터는 TDD 기반으로 더욱 실질적이고 구체적인 테스트 코드 작성을 연습할 계획입니다.리팩토링과 테스트가 결합된 실습을 통해, 코드를 더욱 안정적으로 관리하는 방법을 익힐 예정입니다.이번 주 스터디를 통해 "코드는 언제나 적정한 수준의 추상과 구체 사이에서 균형을 잡아야 한다"는 중요한 원칙을 다시 한번 실감했습니다. 앞으로도 적절한 추상화와 구체적인 구현 사이의 "적정선"을 끊임없이 고민하며, 클린하고 안정적인 코드를 지속적으로 작성하는 개발자가 되도록 노력하겠습니다.감사합니다!

백엔드인프런워밍업클럽스터디백엔드클린테스트코드발자국회고3기

lch9502

워밍업 클럽 3기 BE - 발자국 2주 차

1. 들어가며어쩌다보니 2추 차까지 왔네요? 이번주는 일이 많아서 정신없이 보냈습니다.게다가 클린코드 리팩토링 실습 미션이 있었습니다.아쉽게도 리팩토링을 깔끔하게 마무리 짓지 못해서 코드 리뷰 신청은 못했네요.. 후회할 일이 늘어났습니다... 😢어쨌든 미션 덕분에 지난주 강의를 꽤 여러번 반복해서 들었는데요, 생각보다 더 많은 인사이트를 얻었습니다.그리고 갈 길이 참 멀다는 사실도 알게 되었죠 ㅎ... 2. 학습했던 내용 나만의 키워드로 작성하기섹션 6. 코드 다듬기좋은 주석주석이 많다는 것은 가독성이 좋지 않은 코드주석이 필요한 경우는 히스토리를 알 수 없을 경우변수와 메서드 나열 순서변수는 사용하는 순서대로공개 메서드를 상단에 위치하고, 비공개 메서드 하단에 위치패키지 나누기기능 유지보수하기기능 유지보수하기정렬 단축키, linting, style - sonarlint, editorconfig   섹션 7. 리팩토링 연습Optionalreturn null / Optional 파라미터 사용은 안티패턴객체에 메시지 보내기공개 메서드를 통해 객체끼리 협력하기객체의 책임과 응집도 - IO 통합, 일급컬렉션, display(), Order 추출추상화 관점의 차이 - FileHandler구현에 초점을 맞춘 추상화 VS 도메인 개념에 초점을 맞춘 추상화  섹션 8. 기억하면 좋은 조언들능동적 읽기 오버 엔지니어링구현체가 하나인 인터페이스너무 이른 추상화은탄환은 없다클린 코드도 은탄환 x항상 정답인 기술은 없음. 경험이 중요! 섹션 3. 단위 테스트수동 테스트, 자동화 테스트 Junit5, AssertJ단위 테스트: 작은 코드 단위(클래스 or 메서드)를 독립적으로 검증하는 테스트Junit5: 단위 테스트를 위한 프레임워크AssertJ: 테스트 코드 작성을 원활하게 돕는 테스트 라이브러리해피 케이스, 예외 케이스항상 예외 케이스가 있는지 고민하고 테스트를 작성해야 함경계값 테스트먼저 테스트 케이스를 세분화하고 경계값이 존재하는 경우는 경계값에서 항상 테스트를 할 수 있도록 고민을 하는게 중요테스트하기 쉬운/어려운 영역 (순수함수)관측할 때마다 다른 값에 의존하는 코드우리가 작성한 코드가 외부 세계에 영향을 주는 코드 섹션 4.TDD: Test Driven DevelopmentTDD테스트를 먼저 작성한 후에 그를 통해 기능을 만들고 그 다음에 기능을 수정할 때 테스트의 도움을 받을 수 있도록 하는 개발 방법론 중에 하나레드 - 그린 - 리팩토링레드: 실패하는 테스트를 먼저 작성하는 단계그린: 테스트가 통과할 수 있는 최소한의 코딩을 하는 단계리팩토링: 테스트를 통과하는 것을 유지하면서 구현 코드를 개선하는 단계섹션 5. 테스트는 []다@DisplayName - 도메인 정책, 용어를 사용한 명확한 문장DisplayName을 섬세하게!1. 명사의 나열보다 문장으로2. 테스트 행위에 대한 결과까지 기술하기3. 도메인 용어를 사용하여 한층 추상화된 내용을 담기4. 테스트의 현상을 중점으로 기술하지 말 것Given / When / Then - 주어진 환경, 행동, 상태 변화Given : 시나리오 진행에 필요한 모든 준비 과정 (객체, 값, 상태 등) When : 시나리오 행동 진행Then : 시나리오 진행에 대한 결과 명시, 검증TDD vs BDD 3. 학습 회고컴퓨터 공학과를 나왔고 실무에서 몇 년을 일했지만 객체 지향으로 코드를 작성하는 방법은 배운적이 없는 것 같습니다.캡, 추, 상, 다가 뭔지는 알고, SOLID가 뭔지는 알지만 코드에 잘 적용이 됐는지는 항상 의문이였죠.이 강의는 정말 기대했던 것 보다 더 많은 인사이트를 얻는 강의였습니다. 아마 당분간은 몇 번 더 돌려볼 것 같네요.그래도 지금까지 여러 블로그나 책들을 읽으면서 객체 지향적으로 코드를 작성하려고 노력했었는데요, 강의와 어느정도 결이 비슷하더라구요. 가는 길이 얼추 올바른 길이였다는 사실이 안심이 됩니다. 테스트 코드 강의는 정말 빠르게 봤던 것 같습니다. 강의가 출시되자마자 바로 봤었죠.그 때는 테스트 코드를 잘 작성하는 게 뭔지 모르기도 했고, 주변에서도 작성하는 사람이 단 한명도 없었기에 정말 도움이 되는 강의였습니다.이번에 속도 3배로 다시 쭉 훑었는데 처음 들었을 때 생각이 많이 났습니다.당시에는 몰랐는데 지금은 테스트 코드를 작성하는데 부담이 없는 것을 보니 성장하긴 했나봅니다. ㅋㅋ  4. 미션 회고미션 Day 7개인적으로 이번 미션에 대해 나에게 점수를 매긴다면 좋은 점수를 주지 못할 것 같네요.강의에서 배운 내용을 제대로 적용하지 못했다는 생각이 듭니다.(변명을 해보자면 시간이 너무 부족했다는... 💦💦) 저는 인터페이스로 빼서 구현하는 방법으로 코드를 꽤 많이 작성합니다. 그리고 객체를 조합으로 풀어서 코드를 작성하는 것을 선호합니다.그래서 리팩토링이 쉬울 줄 알았는데요, 강의 내용을 어떻게든 쑤셔넣으려고 하니까 잘 안되더라구요.. 😭아직은 이론과 실전에 갭이 있다는 뜻이겠죠?더 열심히 해야겠습니다!! 그리고 금요일에 진행된 코드 리뷰는 재미있었습니다. 😆😆😆딴 사람들이 작성한 코드를 편하게 리뷰하는 우빈님한테 감탄했던 것 같네요.역시 세상은 넓고 고수는 많다는?

BE워밍업클럽3기리팩토링테스트코드

천준민

워밍업 클럽 3기 BE 클린코드&테스트 - 2주차 발자국

📖 강의 요약 1⃣ 코드 다듬기 주석의 양면성"좋은 주석"우리가 가진 모든 표현 방법을 총 동원해 코드에 의도를 녹여 내고, 그럼에도 불구하고 전달해야 할 정보 가 남았을 때 사용(메서드,변수,클래스) 네이밍에서 의도가 드러나면 주석을 대신 할 수 있을 것 같다. 변수와 메서드의 나열 순서- 변수는 사용하는 순서대로 나열 - 공개 메서드 끼리 기준을 가지고 배치하는 것이 좋음 - 또한 공개 메서드의 배치에 따라서 비공개 메서드의 순서도 고려 해야 한다. - 상태 변경 >> 판별 >= 조회 "결론"나열 순서로도 의도와 정보를 전달 할 수 있다는 것 패키지 나누기"결론"패키지는 문맥으로써 정보를 제공할 수 있다. 2⃣ 기억하면 좋은 조언들 복잡하거나 엉망인 코드를 이해하려 할때 리팩토링 하면서 읽기공백으로 단락 구분하기필요한 적정 수준보다 더 높은 수준의 엔지니어링이 아닌지 생각해보기 Practical Testing: 실용적인 테스트 가이드 1⃣ 테스트는 왜 필요할까커버할 수 없는 영역 발생, 경험과 감에 의존, 늦은 피드백, 유지보수 어려움"올바른 테스트 코드"자동화 테스트로 비교적 빠른 시간안에 버그를 발견할 수 있고, 수동 테스트에 드는 비용을 크게 절약할 수 있다.가까이서 보면 느리지만 멀리서 보면 빠르다. 2⃣ 단위 테스트 작은 코드 단위를 독립적으로 검증하는 테스트프레임 워크 : Junit / 라이브러리 : AsserJ테스트 코드를 작성 할 때는 해피 케이스만 하는 것이 아니라 예외케이스도 함께 작성 해야한다. 3⃣ TDD : Test Driven Development4⃣"TDD"프로덕션 코드 보다 테스트 코드를 먼저 작성해 테스트가 구현 과정을 주독하도록 하는 방법론 = 테스트 주도 개발선 테스트 - > 기능 구현 💡 미션 [섹션 7. 리팩토링 연습]의 "연습 프로젝트 소개" 강의를 보고, '스터디 카페 이용권 선택 시스템 프로젝트에서 지금까지 배운 내용을 기반으로 리팩토링을 진행해 봅시다' 미션 제출 url : https://github.com/2unmini/readable-code/tree/mission/day7 이번 미션에 대한 목표는 배운 내용+ 내가 알고 있는 내용을 가지고 생각해 봤을 때 이유가 충분 한지 판단하여 리팩토링을 해보았지만 다음 날 결과 비교를 해보았을 때는 많이 달랐지만 리팩토링 과제를 하는 동안 이유에 대해 많이 생각해보는 시간이였다. 💬 회고 👍 잘한 점인프런 가입 이후로 처음으로 강의 완료율 100% 달성일급 컬렉션을 적용하여 리팩토링 한점😮‍💨 아쉬웠거나 보완하고 싶은 점System.out.println()을 메소드를 추출할려고 생각했던 점이 아쉬웠다.배운 내용을 다 적용하지 않고 상황에 맞게 적용해 보고 싶다. 📎출처Readable Code: 읽기 좋은 코드를 작성하는 사고법https://www.inflearn.com/course/readable-code-%EC%9D%BD%EA%B8%B0%EC%A2%8B%EC%9D%80%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1%EC%82%AC%EA%B3%A0%EB%B2%95/dashboard

백엔드3기backend워밍업클럽인프런

lch9502

워밍업 클럽 3기 BE - 발자국 1주 차

1. 들어가며원래 인프런 워밍업 클럽 스터디 1기를 신청하려고 했었는데 업무에 치이다보니 3기까지 미뤄졌네요.테스트 코드 강의가 올라오자마자 바로 완강을 했었는데 정말 도움이 많이 돼서 꼭 한 번 인프런 워밍업 클럽 스터디를 해보자고 생각했었습니다.이제 1주가 지났는데, 신청하길 잘했다고 생각합니다.특히 다른 러너분들 미션을 보는데 역시 고수분들이 많더라구요?혼자 공부하는 것 보단 같이 하는게 성장하는데 도움이 된다는 사실을 또 한 번 느꼈습니다. 2. 학습했던 내용 나만의 키워드로 작성하기섹션 2. 추상추상과 구체추상으로부터 구체를 유추할 수 있어야 함추상화 과정에서 중요한 정보를 부각시켜야 함해석자가 동일하게 공유하는 문맥(context)이 있어야 함즉, ‘적절한 추상화’ 는 해당 도메인의 문맥안에서, 정말 중요한 핵심 개념만 남겨서 표현하는 것이름 짓기 / 메서드 선언부추상화의 가장 대표적인 행위 하나를 꼽으라면 이름을 짓는 것추상화 레벨하나의 세계 안에서는, 추상화 레벨이 동등해야 한다.why? 그래야 코드를 읽기가 수월함매직 넘버, 매직 스트링상수를 추출하는 것도 추상화 행위 섹션 3. 논리, 사고의 흐름뇌 메모리 적게 쓰기 (인지적 경제성)인지적 경제성은 최소한의 인지로 최대의 효율을 내보자는 의미코드를 작성할 때도 마찬가지로 인지적 경제성을 추구하도록 작성하기Early return기억해야 되는 정보가 많은 상태라면 전부 기억하지 않는 방향으로 리팩토링을 하는게 효과적사고의 depth 줄이기무조건 1 depth로 만들기 X추상화를 통한 사고 과정의 depth를 줄이는 것이 중요사용할 변수는 가깝게 선언하기공백 라인공백 라인도 의미를 가짐복잡한 로직의 의미 단위를 나누어 보여주는 역할부정어다른 도메인 표현이나 혹은 다른 영단어를 사용정 안된다면 부정연산자를 써서 사고를 두 번 할 바에는 메서드 자체에 그냥 부정의 의미 (is not, does not, never 등등)를 담는 식으로 활용해피 케이스, 예외 처리예외가 발생할 가능성을 낮추는 것이 가장 중요의도한 예외와 예상 하지 못한 예외를 구분하기가 중요 섹션 4. 객체 지향 패러다임객체, 협력과 책임, 관심사의 분리, 높은 응집도와 낮은 결합도관심사에 따라 객체를 분리하게 되면 각 객체는 특정한 관심사로만 이루어짐특정한 관심사로 이루어진 객체는 높은 응집도를 가지며 다른 객체와 협력할 때 낮은 결합도를 가짐 getter/setter 자제하기, 객체에 메시지 보내기setter는 최대한 지양하기getter를 남발하는 건 무례하면서 폭력적인 행위공개 메서드를 통해 객체끼리 협력하기SOLID: SRP, OCP, LSP, ISP, DIPSRP변경이 있을 때 파급 효과가 적어야 한다.변경이 있을 때 하나의 클래스, 하나의 지점만 고쳐야 한다.OCP인터페이스를 구현한 새로운 클래스를 하나 만들어서 새로운 기능을 구현인터페이스를 구현한 새로운 클래스를 만드는 것은 기존 코드를 변경한게 아니라 확장에는 열려 있고 변경에 닫혀 있다.LSP하위 클래스는 인터페이스 규약을 다 지켜야 한다.하위 클래스를 대신 사용하더라도 의도대로 동작해야 한다.ISP하나의 범용적인 인터페이스보다 적당히 분리된 인터페이스 여러개가 낫다.DIP구현체를 바라보지 말고 인터페이스를 바라봐야한다.추상화에 의존해야지 구체화에 의존하면 안된다.DI/IoCIoC내가 호출하는게 아니라 프레임워크 같은게 대신 호출DI의존 관계 '주입' 섹션 5. 객체 지향 적용하기상속과 조합 조합을 활용하는 것이 더 좋은 설계why? 상속은 결합도가 높기 때문Value Object, EntityVO는 도메인의 개념을 추상화한 객체, 불변성, 동등성, 유효성 검증을 보장해야 함Entity는 식별자가 존재하며, 식별자가 같으면 동등한 객체로 취급일급 컬렉션컬렉션으로 Wrapping한 객체를 뜻Enum상수의 집합, 상수들에 대한 로직을 담을 수 있음추상화와 다형성 활용하여 반복되는 if문 제거. OCP 지키기OCP를 지기키 위해서 변화하는 것과 변하지 않는 것을 구분해서 생각해야 함변하는 것조건 & 행위 (= 구체)변하지 않는 것조건을 만족하는가? / 행위를 수행한다. (= 추상)숨겨져 있는 도메인 개념 도출하기도메인 지식은 만드는 것이 아니라 발견하는 것 3. 학습 회고평범한 기업에서 소위 말하는 좋은 코드를 작성하는 개발자는 생각보다 희귀한 것 같습니다.아쉽게도 시간에 쫓기기 때문이겠죠. 저 역시 시간이 없어 기능 구현만 마무리하고 리팩토링은 뒤로 미루는 경험을 했었고, 지금도 하고 있습니다.후에 리팩토링을 하면 좋겠지만 대부분의 개발자들은 뒤를 돌아볼 시간이 없죠.그래서 혼자 리팩토링을 하더라도 좋은 코드를 보고 배울 기회가 굉장히 적습니다.결국 혼자 보는 시야는 한계가 있기 때문에 어느 순간 갇히는 느낌을 받죠.강의에서는 코드를 순차적으로 바꿔나갑니다.메서드 명부터 SOLID를 적용하는 법까지 어떤 코드가 좀 더 나은 코드인지 방향을 제시하죠.그래서 시야가 넓어진 느낌을 받았습니다.다른 사람이 작성한 코드를 볼 수 있었기 때문이죠. 객체 지향 언어만 사용한 코드가 아니라 객체 지향으로 작성한 코드를요.기회가 있을 때마다 조금씩 적용을 해볼 생각입니다. 그래서 추상화를 적용해 읽기 좋은 코드가 되도록 노력해야겠습니다. 😄 4. 미션 회고미션 Day 2강의를 듣기 전 미션부터 확인을 했습니다.미션을 보자마자 '책'이 좋은 예시라고 생각했었는데 강의에서 바로 언급해서.. 😂무슨 예시가 있을까 생각하다가 사실 추상과 구체는 거의 모든 곳에서 사용된다고 결론을 내렸습니다.보통 어떤 행동을 한 후에 자세하게 말을 하는 경우보단 간략하게 의도만 파악하도록 말하기 때문이죠.그래서 음식을 먹는 행동을 예시로 미션을 제출했습니다. 미션 Day 4미션 Day 4미션 코드를 보자마자 코드가 씁.... 물론 실무에는 이것보다 정도가 심한 코드도 훨씬 많긴 합니다.중복 분기문과 부정 연산자를 포함한 조건식을 리팩토링해서 한 눈에 보이도록 처리했습니다.추가로 개인적으로도 Early return은 코드를 깔끔하게 만들어준다고 생각합니다. 저도 실무에서 코드를 작성할 때 else문을 잘 쓰지 않게 되더라구요.하지만 글을 쓰면서 아쉽다는 생각을 했습니다."Order 객체 안에서 검증 로직을 처리할까?"하다가 미션의 의도에 벗어나나 싶어서 메서드만 작성했는데요,회고를 작성하면서 미션을 다시 읽어보니 원하는 것은 Order를 파라미터로 넘겨서 처리하는 게 아니라 Order 객체 안에서 검증로직을 만들라는 것 같다는 생각이 들었네요. SOLID에 대하여 자기만의 언어로 정리하기는 개인적으로 의미가 있던 미션이였습니다.좋은 코드를 작성하기 위해서 계속 SOLID에 대한 고민을 하고 있었거든요.머릿속으로 생각만 하고 있었는데 글로 정리를 하니까 좀 더 선명해지는 느낌을 받았습니다.그리고 강의의 리팩토링되는 코드를 보면서 제가 생각했던 방향과 얼추 맞았다고 보여져서 다행이였습니다. 😃 

백엔드3기백엔드클린코드워밍업클럽발자국

바다다다

[인프런 워밍업 스터디 클럽 3기 FE] 1주차 미션 회고

1주차 발자국은 여기에: [인프런 워밍업 스터디 클럽 3기 FE] 1주차 발자국발자국 글이 길어져 미션을 따로 회고를 작성하였다.Mission1 - 음식 메뉴 앱Demo (클릭연결)SourceCode (클릭연결) 문제해결첫 미션 이여서 조금 설렜다(?)주어진 과제 안내 동영상에서 기본 화면 + 버튼으로 리스트를 화면에 뿌려주는 형식을 보고 따라 만들어 보았다.맥도날드를 좋아해서 맥도날드의 메뉴로 앱을 구성했다.정적 웹페이지로 구성된 화면이다보니 Github Page를 통해 배포까지해서 Demo화면을 구성하였다.고도화마지막 주차에 React 미션에서 쇼핑몰 앱 구현 미션이 있던데 해당 미션에서 구현한 화면으로 맥도날드 주문 앱을 구성해보아도 괜찮을 거 같다는 생각 회고가장 기본적인 HTML, CSS, JS를 활용해서 만든 웹 페이지라 맛보기에 좋았다.Mission2 - 가위 바위 보 앱Demo (클릭연결)SourceCode (클릭연결)문제해결플레이어는 가위, 바위, 보 3가지 중 한가지를 버튼 클릭 방식으로 제출하고, 컴퓨터는 랜덤으로 가위, 바위, 보를 제출하여 게임의 득점을 계산 한다.10회의 가위 바위 보 게임이 끝나면 최종 결과를 출력하는 앱이다.추가로 컴퓨터와 플레이어의 게임 결과를 출력되도록 화면 구성을 하였다.고도화가위 바위 보를 텍스트가 아닌 아이콘으로 표시하면 사용자가 좀 더 식별하기 쉬울 거 같다.현재는 마지막 단계에서 새로고침을 직접 해야하지만, 새로고침 버튼을 추가하여 사용성을 높일 수 있을거 같다.뒷 부분에서 webSocket을 사용한 채팅 앱도 미션으로 나와있던데 webSocket을 통한 양방향 게임도 구현할 수 있는 하나의 방향이라 생각한다.회고JS문법을 조금 더 많이 활용해볼 수 있었던 미션이다.CSS를 구성하는게 어려웠다ㅠ Mission3 - 퀴즈 앱Demo (클릭연결)SourceCode (클릭연결)문제해결미션 영상에서 안내된 내용대로 구현하려고 했다.여러 개의 답을 랜덤으로 생성하는데 "답이없는 경우"도 있다는 것이다. 우선 정답을 저장해두고 랜덤으로 답안 개수를 생성하면서 저장해둔 정답이 버튼으로 생성되지 않는다면 "답이 없음"버튼을 추가하도록 구현했다.정답을 맞추지 않으면 다음문제로 넘어가지 않도록 하였고, 정답을 제출하면 해당 계산식 문제에서 "?" 물음표가 정답으로 바뀌도록 구현하였다.고도화현재 구현된 버전에서는 정답을 맞추지 않으면 다음 문제로 넘어가지 않기 때문에 "O문제 정답"으로 표기하기 보다 풀이 문제수를 표시하고 있다는게 더 명확할 거 같다.무한대로 문제를 출제하도록 구현하여 문제 풀이의 끝이 없다. 문제수를 설정하고 해당 앱을 시작하는 방법도 생각해볼 수 있겠다.또한, 현재 10이내의 범위에서 덧셈 문제만 출제되고 있는데 좀 더 다양한 퀴즈(수학문제가 아니어도)들을 포함할 수 있다.회고 역시나 CSS로 화면을 표시하는 것에 시간이 많이 필요했다. (버튼 색상 변경, 배경 색 변경 등)alert을 미션에서 처음 띄워보았다. 근데.. 사실 사용자 입장에서는 조금 많이 거슬리는 거 같다. 생각해보면 대부분의 웹사이트에서 alert대신 팝업으로 메시지를 띄워주는 경우가 더 많은 거 같다. Mission4 - 책 리스트 나열 앱Demo (클릭연결)SourceCode (클릭연결)문제해결책 목록에 책을 저장해두고 저장해둔 내용을 아래 테이블에 표시해주는 형태이다.추가적으로 수정기능을 넣었고, 책 배열에서 선택된 객체의 값을 변경하여 이를 구현할 수 있었다.시인성을 높이기 위해 삭제된 책은 책이름이 출력되도록 수정하였다.고도화이게 중요한 책 목록 저장 시스템이라면 DB연동해서 영구적으로 저장해두는게 맞지만, 여기서는 미션이고 JS를 학습하는 목적이니 간단하게 화면에 표시만 했다. 로컬에 저장하는 것을 고려해보면 브라우저 내장 storage를 사용해서 잠깐의 저장 기능도 구현할 수 있지않을까라는 생각을 했엇다.회고앞에 3번 정도 미션을 수행하니 이제 CSS도 조금 적응한 듯 싶다.DB연동을 해보지 못해서 조금 아쉬웠지만, 일단 기초를 다지는 단계이니까 기초에 좀 더 집중하자는 생각. Mission5 - Github Finder 앱Demo (클릭연결)SourceCode (클릭연결)문제해결fetch함수로 github API를 사용하여 사용자의 정보를 얻어올 수 있다.얻어온 데이터를 화면에 적절히 뿌려주면되는 과제 였는데, 추가로 repository로 연결되도록 구현하였다.안내 동영상에는 사용자의 input을 감지하여 입력이 발생될 때 마다 API호출을 해서 화면이 랜더링 되도록 구현한 것으로 추측되는데, 이를 좀 더 효율성을 높이고자 사용자가 input을 입력하고 제출할 때 API call을 한 번 하도록 수정하였다.고도화현재 보여지는 profile정보 등은 간략하게 표시하고 있다.github API를 활용하여 클론 코딩도 가능할 거 같다. 회고외부 API를 호출해서 미션을 진행하며 잘 구현된 API가 화면을 구현하는 데에 많은 도움이 된다는 것.<끝>

프론트엔드워밍업스터디3기프론트엔드미션JSJavascriptHTMLCSS회고

바다다다

[인프런 워밍업 스터디 클럽 3기 FE] 1주차 발자국

수강강의: 따라하며 배우는 자바스크립트 A-ZTip: 워밍업 스터디 클럽을 참여하게 되면 [할인쿠폰]으로 강의를 할인 된 가격에 수강할 수 있다!1주차 강의 수강1주차를 되돌아 보며..이번 주 강의는 Javascript에 기본 문법부터 시작해서 중급 문법과 ES6에서 지원하는 class, promise등의 기능까지 학습하는 시간을 가질 수 있었다.강의가 단순하게 이론내용만 전달되는 것이 아니라 실제 코드를 구성하고 해당 Javascript코드가 HTML화면에서 어떤 동작을 하게되는지 실습과 함께 진행되어서 이해하기 더 쉬웠다. 물론 Javascript를 사용하는게 처음은 아니라 좀 더 여유롭게 강의를 이해할 수 있었지만, 중간중간 잊고 있었던 Javascript내용을 다시 복습하기에 좋은 시간이었다.또한 실습 과제도 강의 내용과 연관성이 있어서 단순 강의만 듣는 것보다 더 성취감을 높일 수 있었다. 강의 내용을 다시 정리해보며..(주로 개인적으로 새롭게 알게된 것, 다시 기억해야하는 것들 위주로 정리했다)var, let, const var를 왜 사용하지 말라는지 명확하게 알 수 있었다. let, const로 좀 더 관리하기 편한 코드를 작성하자.for VS forEach 사실 실제 사용할 때 그냥 편하다고 생각하고 forEach를 많이 사용했던 나의 모습이 떠올랐다. 또한 for..of도 많이 사용했었다. 성능적인 측면에서 데이터의 양이 늘어나면 성능이 떨어진다는 것을 알게되었고, 코드를 작성할 때 왜 사용하는지를 좀 더 고민 해야겠다. (절대적인 것이 아니다. 테스트 환경에 따라 결과가 다를 수 있음) CRP과정: Critical Rendering Path DOM tree 생성 → Render tree생성 → Layout → Paint DOM을 생성하는 것은 비용이 많이 발생하지 않지만, render, layout, paint에서 많은 비용이 발생한다. → 따라서 React에서는 가상의 DOM을 사용해서 최적화className VS classList className:특정 element의 클래스 속성의 값을 가져와서 설정할 수 있다.기존에 className을 전부 지우고 추가한다. classList: 읽기전용 property.classList.add()를 통해 기존 className을 유지한 채로 class명을 추가할 수 있다Event 종류가 너무 많다...이것들을 현업에서 사용가능하게 하려면? 공식 문서를 잘 찾아봐야 할 거 같다.어떤 기능을 구현하려고 하는데 이미 지원하는 메서드가 존재하지 않을까?를 생각해보자.Event Delegation (이벤트 위임):하위 요소의 공통 이벤트를 상위 요소에 위임함(강의 듣다가 신기한 코드 발견!)<div id="buttons"> <p>123</p> </div> <script> const buttonList = document.querySelector("#buttons"); buttons.addEventListener("click", () => alert("clicked")); </script> Q: 여기서 buttons는 HTML 요소의 id값 인데 js에서 그대로 사용해서 접근할 수 있다?A: 요소의 id또는 name은 window객체의 property로 추가되어 그대로 사용할 수 있지만, 다른 window객체의 property와 충돌될 수 있으니 사용하지 말자. (결론: 이렇게 쓰지마) JS에서 method VS function (이부분이 강의에서 언급되었는지는 잘 모르겠다. 강의 들으면서 궁금해서 찾아봤다) method: 호출방식, 함수를 호출하는 객체가 있는 경우, 즉 사용자가 정의한 객체의 property가 함수인 경우 해당 함수는 method라고 한다. function: 함수를 호출하는 객체가 없는 경우 Lexical this: 화살표 => 함수에서 this는 상위 스코프를 가리키게된다. bind, call, apply this를 function안에서 사용하면 window객체를 참조하게 되는데, 이를 다른 객체를 참조하도록 도와주는 함수들call(): 함수를 호출하는 함수const fullName = function(city, country){ console.log(this.firstName + ' ' + this.lastName, city, country); } const person1 = { firstName: 'John', lastName: 'Smith' } fullName.call(person1, "Oslo", "Norway") // call함수를 통해 person1 객체를 함수로 넘겨줌 apply() - 배열로 함수의 parameter를 전달함const fullName = function(city, country){ console.log(this.firstName + ' ' + this.lastName, city, country); } const person1 = { firstName: 'John', lastName: 'Smith' } fullName.apply(person1, ["Oslo", "Norway"]) // call함수와 달리 배열 형식으로 넘겨주는 방식 bind() - binding만으로 호출이안되고 새로 call을 해줘야함.function func(language){ if(language === 'kor'){ console.log(`language:${this.korGreeting}`) } else { console.log(`language:${this.engGreeting}`) } } const greeting = { korGreeting: '안녕', engGreeting: 'Hello' } const boundFunc = func.bind(greeting) //binding 만 시켜줌 따라서 새로운 값에 할당하고 호출해야함 boundFunc('eng') Javascript는 동기 언어이다. 어떻게 비동기를 실행가능한가? setTimeout()은 JS가 아니다. 브라우저에서 사용하는 것이면 브라우저 API를 사용하는 것(Window Object), Node에서 사용한다면 Node API를 사용하는 것이다(global Object) JS엔진 Memory Heap: 메모리 할당이 발생하는 곳 (변수를 정의하면 저장해두는 곳) Call Stack: 코드가 실행될 때 스택들이 쌓이는 곳 -> 여기서 이벤트 루프가 call stack, callback queue를 주시하고 있다가 call stack이 비어지면 callback queue에 대기중인 setTimeout작업을 실행하게 됨.setTimeout()을 0초로 설정하면 0초(즉시 실행)를 보장할까?Nope! 이벤트 루프를 타고 callback queue를 들렀다가 오는거니까 즉시실행은 아님.Closure다른 함수 내부에 정의된 함수(innerFunction)이 있는 경우 외부 함수(outerFunction)이 실행을 완료하고 해당 변수가 해당 함수 외부에서 더 이상 엑세스할 수 없는 경우에도 해당 내부 함수는 외부 함수의 변수 및 범위에 엑세스 할 수 있다.function outerFunction(outerVariable) { return function innerFunction(innerVariable) { console.log("Outer Variable: " + outerVariable); console.log("Inner Variable: " + innerVariable); }; } const newFunction = outerFunction("outside"); newFunction("inside"); // 여기서 outerVariable은 여전히 outside를 기억하고 있다.Map, Filter, Reduce Map(): 배열내의 모든 요소 각각에 대하여 주어진 함수를 호출한 결과를 모아 새로운 배열을 반환함Filter(): 주어진 함수의 테스트를 통과하는 모든 요소를 모아 새로운 배열로 반환 Reduce(): 배열의 각 요소에 대해 주어진 리듀서(reducer)함수를 실행하고, 하나의 결과값을 반환한다.undefined VS null 원시 자료형 undefined타입은 undefined이 유일하고, null타입은 null값이 유일하다. undefined는 자바스크립트 엔진이 변수를 초기화할 때 사용한다. 개발자가 의도적으로 undefined를 할당하는 것은 권장하지 않는다. null: 비어있는, 존재하지 않는 값을 의미 NULL, Null과는 다르다! 변수 값이 없을 경우 명시하기 위해서는 undefined가 아닌 null을 사용한다null을 할당하면 변수가 이전에 참조하던 값을 명시적으로 참조하지 않겠다고 하는 것이므로, JS엔진이 이 변수에 메모리 공간에 대해 가비지 콜렉션을 수행한다. (garbage collection: 더이상 사용하지 않는 메모리를 자동으로 정리하는 것)얕은 비교 VS 깊은 비교숫자, 문자열 등의 원시 자료형은 값을 비교한다배열, 객체 등 참조 자료형은 값 혹은 속성을 비교하지 않고, 참조되는 위치를 비교한다.객체의 경우 깊은 비교가 필요함- 깊이가 깊지 않을 경우: JSON.stringify()- 깊이가 깊을 경우: lodash라이브러리의 isEqual()사용함수 표현식(Expression), 함수 선언문(Statement)함수표현식 let funcExpression = function() {...}함수선언문 function funcDeclaration() {...}차이: 함수 선언식은 호이스팅에 영향을 받지만, 함수 표현식은 호이스팅에 영향을 받지 않는다.  강의 내용을 들으면서 아쉬웠던 점..우선 단기간에 많은 양의 강의를 들어야해서 강의에서 진행한 실습을 전부 따라하기에는 힘들었다. 기존에 JS에 대한 기초 지식이 있어서 강의를 다 따라올 수 있었지만, 처음 스터디에 참여하신 분이라면 많이 힘들었을 수 있겠다는 생각을 했다. (특히 직장인은.. 더..)자기 주도학습인 스터디 형식이다 보니 미션 과제를 제출해서 과제 전체 내용에 대한 코드리뷰는 받기 어려웠다. 그래서 미션 과제를 진행하면서 어떤 기능을 더 추가할 수 있을까, 어떻게 하면 편의성을 높일 수 있을까를 많이 생각했던거 같다. 또한 과제 제출시간에 맞춰서 미션을 수행해야 하다보니 코드가 생각보다 깔끔하게 짜여진거 같지는 않았다. (이 부분은 추후에 시간나면 리팩토링 진행할 생각이다..)다음주는 React 프로젝트가 시작되는데,, 조금 걱정이긴하다.. 잘 할 수 있을까? 1주차 미션 완료(글이 길어져 미션에 대해서는 간략하게 시연 위주로 작성하고, 회고는 여기 링크에 따로 작성해두었다.)https://www.inflearn.com/blogs/94751주차 미션은 주로 강의내용에 기반해서 HTML, JS, CSS를 사용해서 Single-page application을 작성하는 과제들이었다. HTML, CSS는 강의에서 다루는 범위가 아니고, JS를 중점으로 다루고 있기때문에 JS의 문법 동작성을 확인하는 데에 더 초점을 두었다고 생각한다. 따라서 과제를 수행할 때 HTML, CSS는 최소한으로 간략하게 작성하고 script파일 작성에 좀 더 중점을 두었다. 제출한 미션은 Github Page를 사용해서 배포하였다.Demo: https://rim0703.github.io/React-study/Source Code: https://github.com/rim0703/React-study/tree/main/Javascript Mission1 - 음식 메뉴 앱(맥도날드를 좋아해서 맥도날드 메뉴판을 간략하게 구성해보았다. 뭐든 관심있는 주제가 더 재밌지 않은가?)Mission2 - 가위 바위 보 앱(과제 참고 영상에서는 컴퓨터에 대한 결과가 출력되지 않아 추가로 컴퓨터의 결과와 플레이어의 결과를 비교할 수 있도록 구현하였다.)Mission3 - 퀴즈 앱(오답 시 다음 퀴즈로 넘어가지 않도록 구현하였다) Mission4 - 책 리스트 나열 앱(삭제 기능 외에 수정 기능을 추가 구현하였다) Mission5 - Github Finder 앱(사용자input을 감지해서 API call하는 대신, 버튼을 통해 input을 제출하면 API call하는 방식으로 개선하였다)(예시 화면은 향로님 깃헙 ^^7) 마지막으로 1주차 회고전반적으로 확실하게 자기주도 학습이 강한 느낌을 받았다. 본인의 일정에 맞춰서 일주일 내에 강의를 수강하고, 과제를 제출하는 것이 쉬운 일이 아닌거 같다. 그럼에도 첫 주를 잘 마무리 할 수 있어서 뿌듯하다.처음에는 React에 대해 학습하고자 스터디에 참여하게 되었지만, 실제 기초적인 Javascript에 대한 내용도 잊은 부분이 많다는 것을 깨닫게되었다.진도표에 보면 다음 주에는 JS강의가 끝나고 React 강의가 본격적으로 시작된다.React 미션과제는 또 어떤 구현 내용들이 기다리고 있을까?기대반 걱정반이다.. 다음주도 화이팅해보자! <끝>

프론트엔드워밍업스터디3기프론트엔드1주차발자국JavascriptHTMLCSSReact

whdudwo1143

인프런 워밍업 클럽 스터디 3기 - 백엔드 클린 코드, 테스트 코드 1주차 발자국

섹션2. 추상추상화란 구체적인 요소들을 감추고 중요한 특징만을 표현하는 것이다. 특정한 측면 외 나머지는 버린다는 점이 인상적이었습니다.또한 추상화에도 각각의 레벨이 있다는 생각을 해본적이 없었는데, 동일한 내부 세계에서는 동일한 추상화 레벨을 가진다는 내용이 인상적이었습니다.섹션3. 논리, 사고의 흐름하나의 문단에는 하나의 의미를 가지고있고, 하나의 메서드에는 하나의 기능만 가져야한다는 내용이 인상적이었습니다.Early Return불필요한 else를 제거하고, 조건에 맞으면 즉시 return하여 흐름을 명확하게 유지하는 방법을 배웠습니다.사고의 깊이(depth) 줄이기중첩된 분기문과 반복문의 깊이를 줄이고, stream을 활용하여 선형적으로 코드를 읽을 수 있도록 개선하는 방법을 익혔습다.부정어 최소화부정 연산자(!) 사용을 줄이고, 긍정적이고 직관적인 표현으로 메서드 이름을 짓는 것이 가독성 향상에 도움이 됨을 깨달았습니다.미션 해결 과정Day 2 미션"추상과 구체 사이의 적절한 균형을 찾는 것이 중요하다."코드가 지나치게 구체적이면 가독성이 떨어지고, 반대로 너무 추상적이면 실제 동작이 모호해진다는 점을 고민할 수 있어서 유익했습니다.Day 4 미션리팩토링 미션을 진행하며, 중첩된 for문과 if문을 Early Return 방식으로 변환하여 코드를 더 읽기 쉽고 직관적으로 만들었습니다. 또한, 가독성을 고려한 메서드 명명법을 고민하면서 개선해 나갔습니다.또한, SOLID 원칙을 나만의 방식으로 정리하는 미션을 수행하면서 단순 암기가 아니라 스스로 이해하며 정리하는 과정을 거쳤다. 여러 자료를 찾아보면서 개념을 내 언어로 표현한 것이 큰 도움이 되었다.회고추상과 구체 사이의 적절한 지점을 찾는 것이 중요느것을 깨닫게 되었습니다. 제가 가지고 있는 개발하며 안좋은 습관중 하나는 한 번에 모든 것을 처리하려다 보니, 하나의 메서드에 여러 기능을 넣는다는점이었습니다. 이번 수업을 통해 이를 반성하고 채움보다 비움을 통해 더 명료하게 개발하는 것의 중요성을 실감할 수 있었다.실습과 미션을 통해 이를 직접 경험하며, Early Return과 중첩 최소화 등 작은 코드 개선만으로도 가독성이 크게 향상될 수 있음을 체감할 수 있었다.스스로 칭찬하고 싶은 점미션을 통해 배운 내용을 즉시 적용하여 리팩토링하면서 개념을 내 것으로 만들려고 노력한 점추상화 레벨을 생각하며 개발하려 노력한 점하나의 메서드는 하나의 기능만 하도록 개발하려 노력한 점아쉬웠던 점 & 보완할 점변수명과 메서드명을 더 명확하고 직관적으로 짓는 능력이 부족하다고 느낌추상화 레벨을 동일하게 맞추는 방법을 더 깊이 있게 연습할 필요성을 느낌다음 주 학습 목표개인 프로젝트를하며 배운내용을 실전에 사용하는것을 목표로 삼고있다.

백엔드워밍업클럽백엔드클린코드3기박우빈1주차발자국발자국

LIMC

[인프런워밍업클럽3기] 백엔드프로젝트 발자국 1주차

항상 프로젝트를 시작하면 초기 세팅이 어려운 것 같다.처음 사용하는 툴과 언어로 많이 헤맸다.. ✔강의이번 주 강의 진도 : 섹션 1~3 엔티티개발기본 개념들과 개발 환경 구성, 프로젝트 생성을 했다.최근에 했던 유니티는 폴더만 넣고 깃 연동이 바로 되었는데 .gitignore파일도 수정해야하고 따로 VCS(버전관리시스템)에 추가해야하는 번거로움이 있었다. entity 패키지테이블은 entity 패키지 안에 클래스로 생성하고 테이블의 각 컬럼은 엔티티의 필드가 된다.repository 패키지데이터베이스와 직접적으로 상호작용하는 레이어(쿼리를 보냄) 사실 아직 클래스에 관해서는 다 이해하지 못했다.. 그냥 따라 적기.. 클래스 작성 중 위와 같은 오류들이 생겼는데 어노테이션 작성 시 자동 완성이 다른 패키지를 사용하는 걸로 들어가서 그런 것이였다. ✔미션이번 주 미션 진도 : 미션 1~2미션 1) 깃허브 리포지토리에 프로젝트 올리기미션 주제를 뭘로 할까 고민하다가 온라인 쇼핑몰 주문 관리 시스템으로 정했다.https://github.com/PO0OH/storevibe/commit/f8e5b96ea93dc308ebc2d426ab8625a1aa9e2ff1 미션 2) 테이블 설계하기ERD - https://www.erdcloud.com/ 사용테이블 연결 시키면 fk가 자동으로 설정되는 줄 모르고 컬럼에 다 입력했다가 삭제했다ㅎ..https://github.com/PO0OH/storevibe/blob/main/README.md 

백엔드인프런워밍업클럽3기발자국백엔드프로젝트

채널톡 아이콘