블로그

Taeho

인프런 워밍업 클럽 - CS Day 13

Algorithm한 번 정렬이 진행될 때마다 피벗이 정렬되고 정렬될 배열을 좌우로 나눠 재귀호출하여 모든 원소를 정렬한다.Quick Sort구현 방법피벗 선택배열에서 피벗으로 사용할 요소를 선택일반적으로 첫 번째, 마지막, 또는 중간 요소를 선택분할 (Partition)피벗을 기준으로 배열을 두 부분으로 나눈다.피벗보다 작은 요소들은 왼쪽으로, 큰 요소들은 오른쪽으로 이동재귀 호출분할된 두 부분 배열에 대해 Quick Sort를 재귀적으로 적용한다.종료 조건부분 배열의 크기가 1 이하가 될 때까지 재귀를 반복한다.결합정렬된 부분 배열들이 자동으로 하나의 정렬된 배열로 합친다.시간 복잡도평균 케이스: O(n log n)최악의 케이스: O(n^2)최선의 케이스: O(n log n)장점공간 효율성: 추가 메모리 공간을 거의 필요로 하지 않는 제자리 정렬 알고리즘캐시 친화적: 지역성이 좋아 캐시 성능이 우수병렬화 가능: 분할 정복 접근 방식으로 인해 병렬 처리에 적합단점불안정 정렬: 동일한 키 값을 가진 요소들의 상대적 순서가 보존되지 않을 수 있다.최악의 경우 성능: 피벗 선택이 잘못되면 O(n^2)의 시간 복잡도를 가질 수 있다.재귀적 특성: 재귀 호출로 인해 스택 오버플로우가 발생할 수 있다.OS주변 장치여러 주변장치는 메인보드 내의 버스로 연결된다.- 기존에는 하나의 버스로 연결되었음→ CPU가 I/O 작업을 수행할 때 다른 작업을 할 수 없음→ I/O Controller(입출력 제어기)와 여러 개의 버스가 추가되었다.입출력 제어기입출력 버스에서 온 데이터를 메모리로 옮긴다.→ 메모리는 CPU의 명령으로 움직인다.→ 입출력 제어기가 메모리에 접근하기 위해선 CPU가 필요하다.→ 입출력 제어기가 CPU의 도움이 필요 없도록 DMA(Direct Memory Access) 제어기가 추가되었다.DMA 제어기를 통해 데이터를 직접 메모리에 저장하거나 가져올 수 있다.CPU와 DMA가 사용하는 메모리가 겹치지 않도록 구분한다.(=Memory Mapped I/O)입출력 버스세부적으로 느린장치와 빠른장치를 구분하기 위해 두개의 채널로 나뉜다.고속 입출력 버스저속 입출력 버스→ 속도 차이로 인한 병목현상을 해결한다.시스템 버스고속으로 작동하는 CPU와 메모리가 사용한다.입출력 버스주변장치가 사용한다.그래픽 카드매우 대용량의 데이터를 다룬다.→ 고속 입출력 버스도 감당할 수 없다.→ 시스템 버스에 바로 연결해 사용한다.내부 구조메인보드에 있는 버스로 연결된다.하나의 버스는 Address 버스, Data 버스, Control 버스로 구성되어 있다.→ I/O 디바이스는 세 세가지 버스를 따로 받을 수 있다.H/W에 맞게 외부 인터페이스가 존재한다.레지스터 : 입출력 작업을 할 때 데이터를 저장하는 역할을 수행CPU 작업을 위해 메모리로 값이 이동하기도 한다.데이터의 전송단위에 따른 분류캐릭터 디바이스상대적으로 적은 양의 데이터를 전송한다.마우스, 키보드, 사운드 카드, 직렬/병렬 포트, etc..블록 디바이스많은 양의 데이터를 전송한다.HDD, SSD, 그래픽 카드, etc...광학 마우스마우스 하단부의 작은 카메라가 초당 1500회 이상의 사진을 찍어 마우스의 디바이스 컨트롤러 내의 DSP(Digital Signal Processor)로 전송한다.DSP는 사진을 분석해 마우스의 x, y축 움직임을 캐치한다.DSP가 마우스의 움직임과 클릭같은 데이터를 감지하면 디바이스 컨트롤러는 CPU에게 인터럽트를 보내고 마우스 드라이버가 동작해서 데이터를 읽어간다.마우스 드라이버는 OS에게 이벤트를 주고, OS는 이벤트를 Foreground 애플리케이션으로 전달한다.키보드사용자가 키보드를 누르면 키보드의 디바이스 컨트롤러가 어떤 키를 입력받았는지 알아낸다.디바이스 컨트롤러가 CPU에게 인터럽트를 보낸다.키보드 드라이버는 OS에게 이벤트를 보낸다.OS는 이벤트를 Foreground 애플리케이션으로 전달한다.HDD블록 디바이스의 한 종류구조Spindle : 막대Platter : 자기화된 원판여러 개의 track으로 구성표면에 자성이 있다.N극 : 0S극 : 1→ 자석을 갖다대면 데이터가 손상된다.일반적으로 플래터 수는 2개 이상Read/Write Head : 플래터의 표면을 읽는다.Cylinder : 여러 개의 플래터에 있는 같은 트랙의 집합Track : Platter를 구성하고 있는 부분Sector : Track을 구성하는 부분HDD의 가장 작은 단위Disk Arm : Read/Write Head가 붙어 있는 장치데이터를 읽는 방법User Process가 HDD의 특정 섹터에 접근 요청실린더 C로 가서 트랙 B에 있는 섹터 D를 읽어들여라.Seek 동작 수행디스크 암은 헤드를 실린더 C로 이동시킨다.Seek Time 헤드를 실린더로 이동시키는 걸리는 시간→ HDD가 느린 이유트랙B의 섹터D가 Read/Write Head에 닿을 때 까지 스핀들을 회전시킨다.헤드에 섹터D가 읽히면 작업 완료Flash Memory(SSD)블록 디바이스의 종류전기적으로 데이터를 읽어들인다.특정한 지점에 데이터를 쓰면 덮어쓰기가 불가능하다.데이터를 지우기 가능한 횟수가 정해져있다.→ 같은 지점에 지우기를 자주 사용하면 망가져 사용할 수 없다.

알고리즘 · 자료구조워밍업클럽CS전공지식DAY13

김예지

[인프런 워밍업 스터디 클럽 2기 FE] 3주차 과제 - 퀴즈 앱 만들기

 퀴즈앱 만들기github : 11-quiz   기능목록퀴즈 풀었을 때 → 정답버튼 띄우기 → 답 확인 기능 플로우/question 에서 문제 2개 랜덤 출력/state 에서 선택된 카테고리의 문제 랜덤 출력/quiz 에서 선택 카테고리 문제 출력 & 진행률 & 결과 보여주기 구현하기폴더구조📁public ├── 📁data │ └── questions.json 📁src/app ├── layout.tsx ├── page.tsx ├── 📁components │ └── Questions.tsx //문제 UI ├── 📁hooks │ └── useQuestionHandler.ts ├── 📁question │ └── page.tsx ├── 📁quiz │ └── page.tsx ├── 📁state │ └── page.tsx  /components/Questions.tsx /question 페이지와 /state 페이지에서 쓰는 문제 스타일은 똑같아서 컴포넌트를 만들어서 가져다가 사용했다.지금 생각해보니 /quiz 도 비슷한데 class 타입을 다르게 만들어서 적용할걸 그랬나 싶다.interface QuestionProps { question: string; //문제 choices: string[]; //선택지 selectedAnswer: string; //선택한 답변 resultClasses: string[]; //결과 class isButtonActive: boolean; //버튼 활성화 여부 questionAreaClass: string; //질문 영역 class onChange: (event: ChangeEvent<HTMLInputElement>) => void; // input 핸들러 onCheckAnswer: () => void; // 정답 확인 호출 핸들러 questionIndex: number; // 문제 고유 index }Questions 컴포넌트에서 사용할 props를 정의한다.export const Question: React.FC<QuestionProps> = ({ question, choices, selectedAnswer, resultClasses, isButtonActive, questionAreaClass, onChange, onCheckAnswer, questionIndex, }) => { return ( <div className={`question-area ${questionAreaClass}`}> <p>{question}</p> <ul className='question-item'> {choices.map((choice, choiceIndex) => ( <li key={choiceIndex}> <span></span> <input /> <label></label> </li> ))} </ul> <button></button> </div> ); };문제 UI 구조는 이렇게 생겼다. 다 같이 쓰면 코드가 이상하게 써져 span, input, label,button 은 아래 따로 뺐다.<span className={`icon ${ resultClasses && resultClasses[choiceIndex] ? resultClasses[choiceIndex] : '' }`} ></span> 사용자가 답을 선택했을 때 답 앞에 동그라미로 이 답이 정답인지 오답인지 알려주는 용도이다.이런식으로 구분이 된다.<input type='radio' id={} // 고유한 id 설정 name={`question-${questionIndex}`} // 고유한 name 설정 value={choice} // 선택지 값 checked={selectedAnswer === choice} // 선택한 답변 확인 onChange={onChange} // 선택 핸들러 호출 /> id= choice-${questionIndex}-${choiceIndex}한 문제가 가지는 4개의 radio input은 다 같은 name을 가지고 있다. 같은 name을 가지는 요소끼리 하나의 그룹으로 취급되어 같은 그룹 내에서 하나의 라디오 버튼만 선택할 수 있다.<button className={`result ${isButtonActive ? 'active' : ''}`} type='button' onClick={onCheckAnswer} // 정답 확인 핸들러 호출 > 답변을 확인하세요. </button>그룹 내에서 하나의 input을 고르면 답을 확인하라는 버튼이 보여주고 버튼을 누르면 상위 div인 question-area와 span 태그에 class가 붙는다. 정답이면 correct / 오답이면 wrong 붙어 색으로 구분이 가능하다.  /question : 문제 2개 랜덤 출력 페이지 const [questions, setQuestions] = useState<Question[]>([]); // 문제 배열 useEffect(() => { // JSON 파일에서 데이터 불러오기 fetch('/data/questions.json') .then((res) => res.json()) .then((data) => { // 수학 문제에서 랜덤으로 하나 선택 const randomMath = data.math[Math.floor(Math.random() * data.math.length)]; // 국어 문제에서 랜덤으로 하나 선택 const randomKorean = data.korean[Math.floor(Math.random() * data.korean.length)]; // 두 문제를 새로운 배열에 담기 setQuestions([randomMath, randomKorean]); });/public/data/questions.json에 있는 데이터를 가져와 랜덤으로 출력해준다.수학 1문제, 국어 1문제를 출력하도록 설정했다. /state : 선택 과목 문제 랜덤 출력/public/data/questions.json에 있는 데이터를 가져와 랜덤으로 출력해준다.선택한 카테고리의 문제 2개를 랜덤으로 출력한다. // 과목 선택 시 JSON 파일에서 해당 과목 문제를 2개 랜덤으로 선택 useEffect(() => { if (selectedSubject !== '') { setIsLoading(true); // 문제를 불러오는 동안 로딩 상태로 설정 resetState(); // 과목이 바뀔 때 상태 초기화 fetch(`/data/questions.json`) .then((res) => res.json()) .then((data) => { if (selectedSubject === 'math') { const randomMathQuestions = getRandomQuestions(data.math, 2); // 수학 문제 2개 선택 setQuestions(randomMathQuestions); } else if (selectedSubject === 'korean') { const randomKoreanQuestions = getRandomQuestions(data.korean, 2); // 국어 문제 2개 선택 setQuestions(randomKoreanQuestions); } setIsLoading(false); // 문제를 불러온 후 로딩 상태 해제 }); } }, [selectedSubject]); 두 페이지에서 사용하는 사용하는 핸들러는 같다./hooks/useQuestionHandler.ts 를 사용하고 있다. return ( <div className='question'> {questions.map((question, index) => ( <Question key={index} // 고유 키 설정 question={question.question} // 질문 텍스트 전달 choices={question.choices} // 선택지 전달 selectedAnswer={selectedAnswers[index]} // 선택한 답변 전달 resultClasses={resultClasses[index]} // 결과 클래스 전달 isButtonActive={isButtonActive[index]} // 버튼 활성화 상태 전달 questionAreaClass={questionAreaClasses[index]} // 질문 영역 클래스 전달 onChange={(event) => handleChange(event, index)} // 선택 핸들러 onCheckAnswer={() => checkAnswer(index)} // 정답 확인 핸들러 questionIndex={index} // 질문 인덱스 전달 /> ))} </div> );Question 컴포넌트에서 넘어오는 정보로 UI를 핸들링 한다.{ "id": 1, "question": "1 + 1은?", "choices": ["1", "2", "3", "4"], "answer": 1 },만약 내가 이 데이터 문제에서 4번째 답을 골랐다면useQuestionHandler의 handleChange 함수가 호출된다. const handleChange = ( event: ChangeEvent<HTMLInputElement>, index: number ) => { const selectedValue = event.target.value; //input 의 value 값인 4를 가져옴 const newSelectedAnswers = [...selectedAnswers]; newSelectedAnswers[index] = selectedValue; // 선택한 답변 저장 setSelectedAnswers(newSelectedAnswers); /* newSelectedAnswers 배열을 복사하여 선택한 값으로 현재 인덱스(여기서는 3)의 값을 업데이트 selectedAnswers[3]에 "4"가 저장된다 */ const newIsButtonActive = [...isButtonActive]; //버튼 활성화 newIsButtonActive[index] = true; setIsButtonActive(newIsButtonActive); /* 버튼 활성화 상태를 관리하는 배열 newIsButtonActive를 복사하여 현재 질문 인덱스의 값을 true로 설정 */ // 정답 확인 후 사용자가 다시 다른 답을 선택했을 때 class초기화 const resetResultClasses = new Array(questions[index].choices.length).fill(''); setResultClasses((prev) => { const updatedResultClasses = [...prev]; updatedResultClasses[index] = resetResultClasses; return updatedResultClasses; }); // question-area 클래스 초기화 setQuestionAreaClasses((prev) => { const updatedQuestionAreaClasses = [...prev]; updatedQuestionAreaClasses[index] = ''; // 초기화 return updatedQuestionAreaClasses; }); };  위의 코드 외에 useQuestionHandler 에는 UI 상태 초기화 하는 함수, span에 class를 추가해 정답표시를 하는 함수도 있다. 상태 초기화 하는 함수는 나중에 넣었는데 /state 에서 수학 문제를 풀고나서 국어 문제를 호출했을 때 기존 UI class가 남아있어서 class를 제거해주는 함수를 추가로 넣었다. 코드를 더 깔끔하고 효율적으로 써보려고 컴포넌트와 훅을 분리 했는데 넘기고 받는 값이 많다 보니까 내가 쓴 코드인데도 헷갈렸다. 더 깔끔하게 쓰는 방법이 있는지.. 고민해봐야겠다. /quiz : 선택 과목 문제 출력 & 결과 보여주기/quiz 페이지에서 사용하는 상태관리const [selectedSubject, setSelectedSubject] = useState<string>(''); // 선택한 과목 상태 const [questions, setQuestions] = useState<Question[]>([]); // 가져온 문제 상태 const [currentQuestionIndex, setCurrentQuestionIndex] = useState<number>(0); // 현재 문제 인덱스 const [selectedAnswer, setSelectedAnswer] = useState<number | null>(null); // 선택한 답 const [correctAnswersCount, setCorrectAnswersCount] = useState<number>(0); // 맞은 답 개수 const [showQuiz, setShowQuiz] = useState<boolean>(false); // 퀴즈 시작 여부 const [showResult, setShowResult] = useState<boolean>(false); // 결과 화면 표시 여부 여기도 뭔가 많다.. 카테고리를 선택하고 테스트 버튼을 누르면 /state 페이지와 같이 문제를 불러오고 화면에 띄워준다. 문제는 하나씩 띄우고 다음 버튼을 눌렀을 때 넘어가게 만들었다. 마지막 문제일 때는 '다음' 이라는 텍스트가 아니라 '결과 보기' 텍스트를 띄운다. 그리고 상태 관리에 저장 된 값을 가져와 결과를 표시해준다. nextjs도 TypeScript에도 익숙하지 않아서 미션 하는데 시간이 생각보다 오래 걸렸다.앞으로는 자바스크립트보다 타입스크립트를 써서 익숙해져야 겠다는 생각이 든다. 

워밍업클럽

river_bori

인프런 워밍업 클럽 2기 - 백엔드 프로젝트 (Spring, Kotlin) 2주차 발자국

일주일간 학습한 내용 요약개발 - Domain프로젝트 생성Jar로 해야지 스프링부트에서 제공하는 내장 키트를 사용할 수 있다.Dependensies: 프로젝트에서 쓸 외부 라이브러리들을 추가해 주는 작업6개의 라이브러리 추가Spring web: MVC사용 등Thymeleaf: 템플릿과 데이터를 합쳐서 최종적으로 완성된 html 파일을 만들어준다. (없으면 개발자가 html 파일까지 코드를 짜야함)Spring Data JPA: JPA에 껍데기를 씌서 사용성을 높임My SQL DriverH2 Database: 인메모리 DB로 스프링이 켜질때 같이 켜짐(스프링과 같은 메모리 사용), 꺼질때 데이터 사라짐Validation: 검증기능그외Spring Security: 로그인 기능에 사용하지만 지금 설치하면 스프링 킬때마다 로그인 해야해서 일단 설치 제외 IntelliJ 설정프로젝트 스트럭쳐프리퍼런시스 Git과 GithubGit 용어commit: 현재 작업한 내용을 하나의 버전으로 반영(저장)rollback:작업한 내용 이전 버전으로 되돌리기branch: 하나의 프로젝트에서 독립적, 병렬적인 버전으로 가지같이 여러명이 동시에 개발이 가능하게 만듬merge: 서로 다른 branch를 합치는 동작conflict(충돌): merge할 때 하나의 파일이 두 브랜치에서 수정이 발생해, 어떤 수정본을 반영할지 알 수 없는 상황repository: github의 저장소 (=remote repository) push: 원격 저장소로 브랜치를 업로드 하는 동작pull: 다운받는 것Git 명령어git status, add, pull, clone, push, commit -m터미널에서'pwd' 입력 => 폴더의 경로 확인'git init' 입력 => 깃 폴더 초기화'git status'입력 => 깃에서 관리하지 않는 파일들이 빨간색으로 표시됨. 그 중 관리하지 않아도 된는 파일들을 배제하고 등록해줌.'.gitignore' 파일에 입력해서 배제 가능함 => gitignore.io 에서 목록 개발환경 입력 후 복사 가능git add README.md : 특정 파일을 기초적 대상으로 추가하는 명령어git commit -m "first commit" : 현재 변경된 내용을 새 버전으로 반영하는 명령어'-m' 옵션을 통해 ""(쌍따옴표)안에 있는 내용을 커밋 메시지로 입력 git remote add origin http://github.com/주소경로: 원경 저장소를 추가 프로젝트 환경 변수 설정데이터 소스와 jpa설정 => 설정해놓은 값을 복붙함.위와 관련된 중요 개념이런 설정 값들은 보통 상수(=변하지 않는 값)다.DB url 등자바 코드에 " "(literal, 문자열 방식)으로 관리해도 되지만, 같은 값을 여러 클래스에서 사용할 때, 값이 수정되면 모든 클래스에서 사용한 값들을 다 찾아서 수정해줘야 한다. 이때, 하나라도 놓치면 에러가 난다.실제 운영할 때는 개발용 서버, 운영용 서버, 개발용 DB, 운영용 DB로 나눠서 사용한다.서로 다른 서버 컴퓨터에서 똑같은 프로그램이 돌아가는데 서로 다른 DB서버에 붙어있다. 개발DB와 운영DB의 주소는 다르다 => 환경변수(=환경마다 바뀌는 값)개발서버에게 개발DB URL을, 운영서버에게 운영DB URL을 알려줘야하는데 " "(문자열 방식)으로는 관리가 어렵다.Spring Profile과 application.ymlSpring Profile: 스프링은 돌아가는 애플리케이션의 프로필을 정의하는 기능을 제공, 스프링을 실행시키는 시점에서 환경변수로 정의 가능개발 서버에 스프링 프로젝트를 띄울 때 dev라는 프로필로 돌릴거라고 지정하고, 운영서버에 prod라는 프로필로 지정하면, 각자 dev, prod로 세팅이 된다. 아무것도 세팅을 안하면 기본값은 default라는 이름으로 돈다. No active profile set, falling back to 1 default profile: "default" application.yml: 스프링은 프로필마다 환경변수를 설정하는 기능을 제공, application.properties: YML 파일과 똑같은 기능을 한다. (문법이 좀 다르다)기존에 있는 properties 파일을 yml로 변경. => application-default.ymlyml 파일을 복사해서 application-docker.yml 을 만듦.application-{Profile} 형식으로 위와 같이 파일을 네이밍 해주면, Profile에 따라 상수 값 설정이 가능하다.스프링이 실행될 때 프로필이 default면 application-default.yml 에서 환경변수를 가져오고, 프로필 이름이 docker면 application-docker.yml 에서 환경변수를 가져온다.키-밸류 형식으로 등록할 수 있다. 같은 키에 값만 다르게 등록한 것. 소스 코드에는 String DATASOURE_URL_PROPERTY = "spring.datasource.url";로 등록해주면 프로필에 따라 각 키에 맵핑값을 찾아 각 DB와 연결하고 동작한다. ymljpa에 대한 설정open-in-view: false => 나중에 따로 설명show-sql: true => sql을 로그에 보이게 할지hibernate: ddl-auto: create => JPA 엔티티를 바탕으로 jpa에서 데이터베이스에 테이블을 새로 만들어주는 기능, 개발 테스트할 때는 써도 되지만 운영에서는 무조건 None으로.properties: hibernate: format_sql: false => sql 로그를 찍을 때 좀 더 보기 쉽고 이쁘게 만들어주는 것, 근데 한줄로 보이게 false처리# default_batch_fetch_size: 10 => 강의에서 따로 설명 예정datasource에 대한 설정(docker.yml과 내용이 다름)url: jdbc:h2:mem:portfolio => db에 url을 알려주고username: sapassword: => 접속하기 위해 필요한 사용자명과 pw알려준다.driver-class-name: org.h2.Driverh2에 대한 설정(default.yml에만 있다. Mysql에는 아래와 같은 설정이 없기 때문)console: => H2에서 DB에 접속하기 위해 사용하는 H2콘솔enabled: true => 을 사용하고path: /h2-console => 어떤 경로로 접속할 건지 지정해주는 옵션클래스 생성도메인 패키지에서 개발할 클래스들을 미리 껍데기만 만듦포트폴리오 패키지도메인 패키지constant (in 상수 관련 클래스)entity (in 총 11개의 클래스)repository (in 총 8개의 인터페이스)configuration (암호관련-나중에 만듦)entity 패키지 (11개 클래스)BaseEntity(추상클래스):모든 테이블들이 공통적으로 갖는 Created Date Time, Updated Date Time 컬럼들은 각 클래스에 직접 넣지 않고 상속을 활용할 예정@MappedSupercass: 이 어노테이션이 있는 클래스를 상속 받는 엔티티 클래스가 이 클래스 안에 있는 필드들을 해당 엔티티에 있는 테이블의 컬럼과 맵핑 할 수 있다.Achievement: BaseEntitiy클래스를 상속받는다.@Entity: 이 어노테이션을 달아줘야 JPA에서 테이블과 맵핑되는 엔티티 클래스라는 것을 알 수 있다.@Id: JPA엔티티에는 필수인 어노테이션, 필드 위에 입력. (var id가 하나의 필드) @Id를 붙여줘야지 이 필드가 PK라는 것을 알 수 있다.=> match case(설정)을 끄면 자동완성을 도와준다.@GeneratedValue(strategy = GenerationType.{다양}: PK생성 전략을 정해준다. strategy 파라미터를 통해 정한다.{다양}TABLE: pk를 만들기 위한 테이블을 전용으로 만들어 PK생성(?)SEQUENCE: DB가 제공하는 순서대로 번호를 지정해주는 시퀀스라는 기능을 사용(MySQL에서는 사용불가)IDENTITY: 기본 키 생성을 DB에 위임. MySQL의 경우 Auto Increment라는 기능을 이용. => 이거로 사용AUTO: JPA가 내부 알고리즘을 따라 자동적으로 결정하는데 MySQL에서는 AUTO로 하면 앞의 TABLE을 사용한다.@Column(name = "achievement_id"): 이 필드가 DB에서 어떤 이름을 가진 컬럼이랑 맵핑되는지 개발자가 직접 지정해주는 기능.안붙여도 필드는 CamelCase(isCamelCase), DB는 SnakeCase(is_snake_case)로 되어 있으면 알아서 맵핑 컬럼을 찾아준다.테이블 pk는 테이블명_id로 지정하고, 코틀린 엔티티에서는 필드명을 id로만 지정. (나중에 이해 안가면 강의 다시 듣기 (7:00) )엔티티 인스턴스를 사용할 때val achievement: Achievement로 변수명을 해줌. 필드명을 id로 줄이지 않으면 achievement.achievementId로 id를 조회해야 함.achievement.id로 직관적이고 보기도 좋게 사용하고 싶음=> 때문에@Column(name = "achievement_id") var id: Long? = null로 지정자료형 뒤에 ?를 붙이면 null이 허용된다는 의미, 코틀린은 자바보다 null에 대해 엄격하다.id는 엔티티를 처음 생성할 때 들어가지 않고 이 엔티티를 DB에 저장할 때 DB에서 생성해 주는 값이기 때문에 인스턴스를 처음 만든 순간에는 null일 수 밖에 없다.Achievement 클래스를 복사해서 다른 클래스들을 만든다. (@Column의 name등 바꾸기) repository 패키지 (8개 인터페이스)Spring Data JPA RepositoryRepository: DB 접근하는 역할Spring Data JPA: 스프링에서 jpa를 좀더 쉽게 쓰기 위해 한번 랩핑한 라이브러리인터페이스를 추가하는 것만으로 DB CRUD와 관련된 기본적인 기능을 사용 가능각 엔티티에 대응해 interface로 각각 repository를 만들어야한다.BaseEntity 클래스 제외스프링을 시작할 때 SpringDataJPA에서 인터페이스를 보고 알아서 repository 클래스를 만든다. AchievementRepositoryinterface AchievementRepository : JpaRepository<Achievement, Long>JpaRepository<Achievement, Long>를 상속받음.<>을 Generic으로 명칭 나머지 엔티티에 대응하는 레퍼지토리 만들기...Detail 클래스에 대응하는 repository는 안만든다. JPA가 연관관계를 가진 엔티티를 통해서 엔티티를 불러올 수 있기 때문...Skill 클래스는 따로 만들어준다 => 왜? 뭔가 다르데 constant 패키지SkillType: enum 클래스 => 상수값(언어, 프레임워크, BD, Tool) 엔티티 개발 - 연관관계 없음BaseEntity.kt@CreatedDate: JPA엔티티가 생성된 시간을 자동으로 세팅@Column(nullable = false, updatable = false): 지난번 Name 파라미터를 이용해 필드와 맵핑될 Column의 이름을 별도로 지정해주는 기능(@Column(name = "achievement_id")) 설정함. 그것과는 다른 기능을 설정. 위 내용은 null일 수 없고, 변경 불가능 하다는 뜻. (다른 엔티티와) 연관관계가 없는 엔티티Skill 같은 경우, 프로젝트와 프로젝트 스킬을 통해서 연관관계를 가지지만, 스킬을 통해 프로젝트에 직접 접근하는 일이 없다 -> 때문에 연관관계가 없는 엔티티와 다를게 없다.엔티티 같은 경우, 연관관계에 상관없이 생성자를 이용해 처음 인스턴스를 생성할 때 필요한 값들을 전부 받으려고 한다.때문에 기본 생성자부터 만든다생성자(영어: constructor, 혹은 약자로 ctor)는 객체 지향 프로그래밍에서 객체의 초기화를 담당하는 서브루틴을 가리킨다. 생성자는 객체가 처음 생성될 때 호출되어 멤버 변수를 초기화하고, 필요에 따라 자원을 할당하기도 한다. 객체의 생성 시에 호출되기 때문에 생성자라는 이름이 붙었다.[위키백과]Achievement.kt기본 생성자를 만든다id 아래에 필드들을 만든다. -> 생성자에서 받은 값들을 넣어준다. (초기화한다.)Introduction.kt와 Link.kt도 비슷하다.Skill.kt생성자 중 type: String(일단은 문자열로 받음)=> 데이터를 처음 만들 때, 어드민 프론트에서 데이터를 받아서 세팅을 해주는데, 어드민에서는 이런 타입 같은 것을 알 방법이 없기에 문자로 보냄=> 문자로 받고 생성자 내부적으로 타입 스트링에 맞는 스킬 타입을 찾아 필드에 넣어줄 것임.var type: SkillType = SkillType.valueOf(type)SkillType.kt에서 문자열과 일치하는 enum을 찾아서 리턴해줌.jpa에서 활용하려면 좀 더 지정해줘야 함.@Column(name = "skill_type")(type을 예약어로 쓰는 DB가 있기 때문에 테이블 컬럼명으로 'type' 쓰는 것을 지양해야 함.)@Enumerated(value = EnumType.STRING)자료형이 enum클래스일 때 쓰는 어노테이션.EnumType.{STRING|ORDINAL} 두 개 중 선택 가능ORDINAL: enum이 선언된 순서대로 1, 2, 3...의 값을 DB에 넣어줌.1) DB를 봤을 때 직관적으로 이 데이터의 실질적 의미를 알기 어렵다.2) 어떤 개발자가 enum의 순서를 바꿨을 때, 데이터의 정합성이 깨짐STRING: enum의 이름 그대로 DB에 넣음(지정 필수)(DB의 용량을 약간 더 차지하는 단점 존재)HttpInterface.kthttp 요청 정보를 저장하는 엔티티class HttpInterface(httpServletRequest: HttpServletRequest): 스프링에서 요청을 받을 때 그 request의 정보를 여기에 담아서 준다. => 클라이언트 정보를 꺼낸다.var cookies: String? = httpServletRequest.cookies?.map{"${it.name}:${it.value}"}?.toString():.map{ }은 cookies라는 객체가 배열인데 안의 것들을 하나씩 순차적으로 돌면서 {중괄호}안에 들어간 함수대로 변환해주는 기능. it은 cookies 객체. cookies안에 name과 value가 있어 중괄호 안의 방식으로 포맷팅 되어진다.=> 쿠키에는 이용자가 본 내용, 상품 구매 내역, 신용카드 번호, 아이디(ID), 비밀번호 IP 주소 등이 배열로 담겨 있어 위 작업은 그 중 name과 value를 꺼내는 동작이다.(?).toString()으로 문자열로 바꾼다. => "name:value"HTTP 쿠키(HTTP cookie)란 웹 서버에 의해 사용자의 컴퓨터에 저장되는, '이름을 가진 작은 크기의 데이터'이다. 인터넷 사용자가 어떠한 웹사이트를 방문할 경우 사용자의 웹 브라우저를 통해 인터넷 사용자의 컴퓨터나 다른 기기에 설치되는 작은 기록 정보 파일을 일컫는다. 쿠키, 웹 쿠키, 브라우저 쿠키라고도 한다. 이 기록 파일에 담긴 정보는 인터넷 사용자가 같은 웹사이트를 방문할 때마다 읽히고 수시로 새로운 정보로 바뀐다. 이 수단은 넷스케이프의 프로그램 개발자였던 루 몬툴리가 고안한 뒤로 오늘날 많은 서버 및 웹사이트들이 브라우저의 신속성을 위해 즐겨 쓰고 있다. (=> 신속성 = 서버크기 예측..?)쿠키는 소프트웨어가 아니다. 쿠키는 컴퓨터 내에서 프로그램처럼 실행될 수 없으며 바이러스를 옮길 수도, 악성코드를 설치할 수도 없다. 하지만 스파이웨어를 통해 유저의 브라우징 행동을 추적하는데에 사용될 수 있고, 누군가의 쿠키를 훔쳐서 해당 사용자의 웹 계정 접근권한을 획득할 수도 있다.[위키백과]referer: nullable한 필드, http 요청 정보에서 referer을 가져온다. 구글을 통해 검색해 어떤 사이트에 들어갔을 때, google.com의 도메인이 referrer(조회인)가 되는 것임웹 브라우저로 월드 와이드 웹을 서핑할 때, 하이퍼링크를 통해 각각의 사이트로 방문시 남는 흔적, 웹 사이트의 서버 관리자가 사이트 방문객이 어떤 경로로 자신의 사이트를 방문했는지 알아볼 때 유용, referer은 but 조작 가능, HTTP 리퍼러를 정의한 RFC에서 'referrer'을 'referer'로 잘못 입력한 것이 계속 사용됨[위키백과]localAddr, remoteAddr, remoteHost:클라이언트와 관련된 ip 주소들requestUri: 우리 서버에서 어떤 uri로 접속을 했는지, 메인이면 그냥 루트 or /, 프로젝트면 /프로젝트, resume면 /resume 로 어떤 uri로 접속했는지 그 정보가 들어온다. (referer과 다른 점은 어디에서 검색해서 사이트에 들어왔는지와, 사이트에서 이동 경로 추적 차이..?)통합 자원 식별자(Uniform Resource Identifier, URI)는 인터넷에 있는 자원을 나타내는 유일한 주소이다. URI의 존재는 인터넷에서 요구되는 기본조건으로서 인터넷 프로토콜에 항상 붙어 다닌다.URI의 하위개념으로 URL, URN 이 있다. [위키백과]userAgent:사용하는 브라우저 정보, 크롬, 사파리, 모바일, 데스크탑 등등 엔티티 개발 - 연관관계 있음Experience.kt생성자에 초기값을 넣는다.필드를 선언한다.Experience Entity는 ExperienceDetail과 1:N의 관계jpa에서는 List로 N쪽에 해당하는 필드를 가져올 수 있다.@OneToMany(targetEntity = ExperienceDetail::class, fetch = FetchType.LAZY, cascade = [CascadeType.ALL]) @JoinColumn(name = "experience_id") var details: MutableList<ExperienceDetail> = mutableListOf()@OneToMany(targetEntity = ExperienceDetail::class, fetch = FetchType.LAZY, cascade = [CascadeType.ALL]):One은 Experience, Many는 ExperienceDetail. 아래 필드가 1대 다의 관계를 가지고 있다고 jpa에 알려주는 어노테이션(targetEntity = ExperienceDetail::class,...): 어노테이션의 옵션 => targetEntity는 나중에 별도의 강의에서 설명 예정fetch = FetchType.{EAGER|LAZY}:EAGER은 더 열심히고 열정적인 경찰이래, 사건이 일어나면 용의자인 experience를 잡아야하는데, experienceDetail이 자식같은 관계니까 연관된 detail까지 다 잡아온다.개발자가 DB에서 experience만 조회하려고 했는데, detail까지 같이 인스턴스 안에 들어가 있다.그래서 EAGER는 쓰면 안된다. 오래걸린다. N+1의 문제인다(부모를 조회하려고 쿼리가 나가고 그다음 자식이 있다는 것 알고 자식을 조회하려고 쿼리가 N번 더 왔다갔다함, 부모 100명 조회 1번, 자식 100명 조회 100번 => 총 101번 쿼리 발송)LAZY는 좀더 효율적이다. 부모를 조사하다가 자식도 혐의가 있을 때만 잡으러 간다.부모 엔티티에서 실제로 자식 엔티티 필드를 호출하는 그 순간에만 조회쿼리가 나간다. 호출한 부모 엔티티의 자식 엔티티를 모두 조회해야할 때에는 EAGER과 다를 바가 없기 때문에 근본적인 해결책은 안된다.처음부터 부모와 자식을 한꺼번에 조회하는 방법은 레포지토리 개발하면서 설명할 예정cascade = [CascadeType.ALL]:영속성 콘테스트와 관련있는 개념, experience 엔티티가 영속성 콘테스트와 관련해서 발생하는 모든 변화에 자식 엔티티도 똑같이 적용할지 정해주는 옵션. ALL이면 모두 똑같이 적용한다는 뜻@JoinColumn(name = "experience_id")맵핑에 기준이 되는 컬럼을 알려준다.var details: MutableList<ExperienceDetail> = mutableListOf()mutableListOf: 빈 리스트를 만들어 준다.Mutable: '변할 수 있다' 라는 뜻 fun getEndYearMonth():종료연월을 각각 널체크하고 처리하면 서비스 코드가 복잡해지기 때문에 필요한 데이터를 한 번에 깔끔하게 서비스에서 가져올 수 있도록 엔티티 안에서 묶어줌fun update(생성자 모두 받음): put...각각 호출해서 수정하는 것보다 update하나를 호출해서 모두 한꺼번에 데이터 변경 가능하게 함jpa는 엔티티의 데이터를 바꾸기만하면 트랜젝션이 끝날 때, 처음 데이터를 가져올 때 따로 백업했던 스냅샷과 지금 엔티티의 상태를 비교해서 수정된 부분이 있으면 알아서 업데이트를 날린다.fun addDetails(details...):null 체크를 포함한 기본 방어 로직, 사용하는 쪽에서 깔끔한 디테일 데이터 추가 가능ExperienceDetail.kt: 연관관계 없는 엔티티와 비슷experienceDetail만 가지고는 experience를 찾을 수 없는 일대다 단방향 연관관계fun update(content: String, isActive: Boolean):Project.kt, ProjectDetail.kt 는 experience, ...detail과 비슷@OneToMany(mappedBy = "project", fetch = FetchType.LAZY, cascade = [CascadeType.PERSIST]) var skills: MutableList<ProjectSkill> = mutableListOf()@OneToMany(mappedBy = "project", fetch = FetchType.LAZY, cascade = [CascadeType.PERSIST]):mappedBy:양방향 연관관계에서 연관관계의 주인을 지정할 때 사용. ProjectSkill.kt안에 var project를 추가하는데, 이 var project를 통해 맵핑이 되고 맵핑하는 것은 ProjectSkill(연관관계에서 주인)이다.cascade = [CascadeType.PERSIST]:영속성 '전이'와 관련된 설정,cascade를 별도로 지정하지 않을 경우, Project 엔티티를 생성하고 save() 메소드를 호출해 영속성 컨텍스트에 persist한는 등 따로 persist를 해줘도 엔티티에 포함된 skills, 즉 ProjectSkills 엔티티들은 persist 되지 않는다.CascadeType.PERSIST를 지정해주면, Project 엔티티만 persist 해도, 거기 포함된 skills의 엔티티들이 모두 같이 persist가 된다.PERSIST 외에도 DETACH, MERGE, REMOVE, REFRESH 등의 상태를 적용할 수 있다.var skills: MutableList<ProjectSkill> = mutableListOf()ProjectSkill.kt: 다대일의 관계라서 프로젝트와 스킬을 각각 연결@ManyToOne(targetEntity = Project::class, fetch = FetchType.LAZY) @JoinColumn(name = "project_id", nullable = false) var project: Project = project @ManyToOne(targetEntity = Skill::class, fetch = FetchType.LAZY) @JoinColumn(name = "skill_id", nullable = false) var skill: Skill = skill 데이터베이스 초기화프로필소개글 3줄깃허브, 링크드인 링크학력/경력(Experience)수상/자격증(Achievement)기술스택(Skill)프로젝트(Project)사용기술: 기술스택(Skill)과 다대다 관계데이터 초기화 코드 작성도메인 패키지 안에 DataInitializer.kt 생성 (개발 편의를 위해 임의로 만든 것임)총 6개의 repository에 의존한다.생성자로 6개 입력 private val achievementRepository: AchievementRepository 등등 => DataInitializer를 빈으로 등록하려면 생성자인 repository들도 빈으로 등록됨 => 이런식으로 스프링 초기화가 진행됨class DataInitializer( //이게 바로 생성자 주입 private val achievementRepository: AchievementRepository, private val introductionRepository: IntroductionRepository, private val linkRepository: LinkRepository, private val skillRepository: SkillRepository, private val projectRepository: ProjectRepository, private val experienceRepository: ExperienceRepository )@Component: 스프링에서 관리하는 인스턴스 => bean(빈)스프링 처음 실행시 컴포넌트 스캔 과정을 거침이때, 스프링에게 어떤 것을 빈으로 등록할지 알려주는 역할자바로 클래스를 사용하려면 개발자가 직접 생성자를 이용해서 클래스의 인스턴스를 만들어야하는데, 스프링 프레임워크가 개발자 대신 인스턴스 제어를 한다.다른 인스턴스에서 이렇게 만들어진 빈들을 사용하려면 그 인스턴스를 주입받아 사용한다. =>"의존성 주입DI" => DI 방법은 잠시 후 해볼 예정 이런 빈들을 사용할 때에는 생성자, setter, 필드 주입등의 방식을 통해 의존성 주입을 받아 사용 가능@Controller, @Service, @Repository:세 어노테이션에는 component의 기능이 포함되어 있다.@Profile(value = ["default"]):스프링이 빈으로 등록하는데 프로필이 default일 때만 이 클래스를 생성해서 빈으로 등록한다. (개발자가 임의로 데이터 등록 못하게) @PostConstruct:메인 메소드가 실행이 되면서 스프링을 구축한다. 이때 Spring DI가 컴포넌트 스캔을 해서 인스턴스(빈)를 생성하고 의존성을 주입한다. 이런 식으로 스프링 프로젝트를 construct(구축)한다. 이런 스프링 초기화 작업이 완료되면, PostConstruct가 붙은 메소드를 찾아서 한번 더 실행한다(이때는 빈들이 다 등록되어 있어서 필요한 빈을 찾아 사용가능, 그 빈들을 이용해 테스트 데이터를 초기화 함 ). 이게 끝나면 스프링 실행이 완료된 것.fun initializerData(): //이게 아마 메인메소드println(" "): => logger를 써라java의 'System.out.println'과 똑같다.내부적으로 Synchronized를 달고 있어 성능에 좋지 않다. (자원을 하나씩 순차적으로 여러 스레드가 사용하고 있고, 동시에 사용할 수 없다. 그래서 성능에 안좋고 운영에 절대 쓰면 안된다.)logger:출력하려는 내용과 더불어 시간, 스레드 등 여러 정보들이 같이 출력됨. (때문에 강의할 때는 깔끔하게 보기위해 println을 쓸것임)val achievements = mutableListOf<Achievement>(엔티티 2개 입력함):mutableListOf: 리스트로 정의한다.2개의 Achievement Entity를 가진 리스트를 achievements필드에 초기화 함 => 엔티티를 만들어 주입받는 jpa repository들을 이용해 DB에 데이터를 넣어주는 작업achievementRepository.saveAll(achievements-리스트): 레파지토리에 리스트로 insert한다.achievementRepository interface에 아무것도 없는데 메소드에 사용이 가능하다(Spring Data JPA에서 만들어주는 기능이다.AchievementRepository가 상속하는 JpaRepository에 다양한 메소드들이 정의되어 있다.스프링이 실행되면서, 만든 인터페이스와 상속하는 인터페이스들 안에 실제 동작하는 기능을 가진 코드를 가지고 있는 repository 클래스를 만들어준다. 그 클래스들이 빈으로 등록된다.때문에 기본적인 기능들은 - list로 insert하는 것 등등 - 개발자가 하나하나 쿼리를 짤 필요 없이 간단하게 사용 가능하다. ) => 헷갈림 val introductions = mutableListOf<Introduction>(3개의 엔티티): ..복붙experienceRepository.saveAll(mutableListOf(experience1, experience2))experience를 리스트로 만들어 saveAll() 함수로 넘김. saveAll()을 통해 영속성 컨텍스트에 들어감. 현재 트랜잭션이 종료 될 때 영속성 컨텍스트에 있는 내용들이 insert로 DB에 들어감. 그때, Experience가 가진 detail들이 같이 insert로 들어간다.experience1.addDetails( mutableListOf( ExperienceDetail(content = "GPA 4.3/4.5", isActive = true), ExperienceDetail(content = "소프트웨어 연구 학회 활동", isActive = true) ) ) experience2.addDetails( mutableListOf( ExperienceDetail(content = "유기묘 위치 공유 서비스 개발", isActive = true), ExperienceDetail(content = "신입 교육 프로그램 우수상 수상", isActive = true) ) )이 때 만약, Experience.kt 안의 var details에 @OneToMany(targetEntity = ExperienceDetail::class, fetch = FetchType.LAZY, cascade = [CascadeType.ALL])에서 CascadeType을 ALL로 안하면, DB에 Experience는 입력되지만 detail은 insert 쿼리에서 제외된다.만든 엔티티(Skill)를 변수에 다 할당해준다.나중에 Project에서 projectSkill과 연결해서 재사용할 예정임.val java = Skill(name = "Java", type = SkillType.LANGUAGE.name, isActive = true) val kotlin = Skill(name = "Kotlin", type = SkillType.LANGUAGE.name, isActive = true) val python = Skill(name = "Python", type = SkillType.LANGUAGE.name, isActive = true) val spring = Skill(name = "Spring", type = SkillType.FRAMEWORK.name, isActive = true) val django = Skill(name = "Django", type = SkillType.FRAMEWORK.name, isActive = true) val mysql = Skill(name = "MySQL", type = SkillType.DATABASE.name, isActive = true) val redis = Skill(name = "Redis", type = SkillType.DATABASE.name, isActive = true) val kafka = Skill(name = "Kafka", type = SkillType.TOOL.name, isActive = true) skillRepository.saveAll(mutableListOf(java, kotlin, python, spring, django, mysql, redis, kafka))변수로 초기화를 하지 않고Skill(name = "Java", type = SkillType.LANGUAGE.name, isActive = true)생성자만 가지고 skillRepository를 이용해 한번에 DB에 넣으면 나중에 project에서 가져오기 복잡해진다. 그래서 미리 정의해준다.Project는 experience와 비슷addDetails()와 같이, skills.addAll() 함수로 묶어서 project1에 넣어줄 수 있다.// 방법1 project1.addDetails( mutableListOf( ProjectDetail(content = "구글 맵스를 활용한 유기묘 발견 지역 정보 제공 API 개발", url = null, isActive = true), ProjectDetail(content = "Redis 적용하여 인기 게시글의 조회 속도 1.5초 → 0.5초로 개선", url = null, isActive = true) ) ) // 방법2 => 다양한 방법이 있다 project1.skills.addAll( mutableListOf( ProjectSkill(project = project1, skill = java), ProjectSkill(project = project1, skill = spring), ProjectSkill(project = project1, skill = mysql), ProjectSkill(project = project1, skill = redis) ) )val: 불변(Immutable) 변수로, 값의 읽기만 허용되는 변수. 값(Value)의 약자이다.변수를 선언할 때 지정한 값에서 더이상 변경하지 않는 경우var: 가변(Mutable) 변수로, 값의 읽기와 쓰기가 모두 허용되는 변수. 변수(Variable)의 약자이다.변수의 값을 바꿔야 하는 경우출처: https://kotlinworld.com/173 리포지토리 개발JAP엔티티를 미리 정의해 두고 인터페이스만 만들면 Spring이 실행되면서, 리포지토리 인터페이스를 기반으로 리포지토리 클래스들을 만들어서 Spring Bean으로 등록한다.@Entity class Experience(... interface AchievementRepository : JpaRepository<Achievement, Long> {...서비스 Bean에서 리포지토리 빈들을 주입받아서 바로 사용 가능하다. 이때 사용하는 기능들은 Insert, Update, ID로 조회하기, ID로 삭제하기 등이 있다. 특정 컬럼 조회하기 등은 기본 메소드에 없다.인터페이스에 미리 정해진 규칙대로 메소드 이름을 정의해주면, 메소드 이름을 기반으로 쿼리를 작성해준다. => A부터 Z까지의 컬럼이 있을 때, 개발자가 A, B를 조회하고 싶다면 'Find by A and B' 이런 식으로 메소드 이름을 정의하고 파라미터로 A와 B를 넣어 주도록 인터페이스에 메소드를 정의하면 된다.// select * from achievement where is_active = :isActive fun findAllByIsActive(isActive: Boolean): List<Achievement>SkillRepository.kt 에는 메소드를 하나 더 만든다.// select * from skill where lower(name) = lower(:name) and skill_type = :type fun findByNameIgnoreCaseAndType(name: String, type: SkillType): Optional<Skill>Optional<Skill> 로 Skill 단건을 조회하게 함.case를 무시하라고 했기에, 전부 다 대문자나 소문자로 변경(컬럼도) => 뭔가 추가적인 지식이 있는 듯위 내용을 순수한 쿼리로 작성하면, 구체적인 DB 시스템에 종속된다. => 예를 들어, lower 함수 같은 경우. mySQL은 lower이라고 써도 오라클이나 다른 DBMS에서는 같은 기능을 다른 함수로 쓸 수 있기 때문.=> Spring Data JPA에서 이런 부분을 개발자가 신경 안쓰게 하기 위해 'IgnoreCase'로 각각 DBMS에 맞게 변경해줌 리포지토리 테스트 코드 작성테스트 코드는 매우 정말 중요하다.=> 강의용 프로젝트같이 규모가 작은 경우에는 덜 중요할 수 있다.IntelliJ는 특정 클래스의 테스트 클래스를 쉽게 만들어주는 기능을 제공한다.DataInitializerTest.kt[테스트할 클래스 -> 마우스 오른쪽 -> Generate -> test]도메인 등 원래 클래스가 있던 것과 같은 경로로 test패키지 안에 test 클래스가 생성된다.=> DataInitializerTest.kt 삭제 (테스트할 대상이 Spring Data JPA Repository Interface 이기 때문)인터페이스여서 테스트 클래스를 만들 수 없고 같은 규칙으로 직접 만듦test>kotlin>com>bohui>portfolio>domain 안에 패키지 '리포지토리'를 만든다.테스트 코드 작성은 많은 작업이 필요해서 오래 걸린다. => 때문에 찐 테스트 코드를 작성하지 않고, 작성하는 방식을 보여줄 예정Experience와 Project Repository 에 대해서만 테스트 클래스 생성ExperienceRepositoryTest.kt@DataJpaTest @TestInstance(TestInstance.Lifecycle.PER_CLASS) class ExperienceRepositoryTest( @Autowired val experienceRepository: ExperienceRepository // 테스트할 대상을 주입받음 ) @DataJpaTest:jpa 관련 테스트 할 때 사용하는 어노테이션.이 테스트 코드가 실행될 때, jpa 사용이 가능한 만큼 스프링 빈을 만들어 준다.Transactional이라는 어노테이션을 가지고 있다.@Transactional: 테스트 메소드 하나를 하나의 트랜잭션으로 보고, 메소드가 종료될 때 그 트랜잭션에서 발생한 모든 작업을 롤백함.테스트 코드에서 중요 원칙 중 하나는 독립적으로 항상 같은 결과를 내야한다는 것. => 인메모리 DB를 쓰면 상관없지만 안쓰면 롤백해야함. 안하면 테스트 코드 돌릴 때마다 테스트용 데이터가 계속 쌓여 다음 테스트에 영향을 줄 수 있다.=> 때문에 Transactional 어노테이션을 달아서 자동 롤백이 되게 함.@TestInstance(TestInstance.Lifecycle.PER_CLASS):TestInstance.Lifecycle.PER_CLASS:TestInstance의 라이프 사이클이 클래스 단위가 됨.원래 기본값으로, 이 테스트 코드를 돌리는 로직이 NewExperienceRepositoryTest 해서 메소드 한개 돌리고 또 NewExperienceRepositoryTest 해서 두번째 메소드 돌리는 식으로 수행이 됨. (같은 클래스에 있는 메소드들 이지만, 메소드 마다 인스턴스 생성)라이프 사이클을 클래스 레벨로 해주면, 인스턴스를 한번 만들어서 그 안에 있는 여러 메소드들을 수행한다.그래도, 메소드마다 TestInstance를 만들어 테스트를 돌리면, 메소드간 의존적이지 않다는(독립적) 장점이 있다.내부적 메소드 간에 의도적으로 의존적이게 만들고 싶을 때, 클래스를 만들어서 메소드를 1번, 2번, 3번 다 돌리는게 낫다...(이해가 더 필요)@BeforeAll: 테스트 데이터를 초기화하는 메소드. 다른 메소드가 돌기 전에 제일 처음에 딱 한번 돌아야 함. 때문에, TestInstance의 라이프 사이클을 클래스 단위로 해줘야 함.'DataInitializer.kt'는 개발 편의를 위해 임의로 만든 것으로, 이렇게 데이터를 초기화하는 방식은 좋지는 않다(왜?). 때문에 독립적으로 사용하기 위해 BeforeAll로 초기화할 예정. 그리고 @DataJpaTest를 사용하면 그 스프링 데이터 jpa를 테스트하기에 필요한 기능들만 초기화 가능하다. 그래서 테스트 돌릴 때, DataInitializer의 내용들은 빈으로 등록 안됨.TestInstance.Lifecycle.PER_METHOD:라이프사이클을 메소드로 할때, BeforeAll이 돌아 초기화를 해줬지만 다음에 돌아가는 테스트 메소드들은 BeforeAll의 영향을 받을 수 없다.@Autowired val experienceRepository: ExperienceRepository: 생성자로 테스트할 대상을 주입받음private fun createExperience(n: Int): Experience:테스트 데이터 초기화를 할 때 더미 엔티티를 만들어주는 기능, 받은 'n'의 개수만큼 이 'Experience' 안에 디테일을 넣어준다.val experience: 비어있는 더미 객체(entity) 생성기능단위로로 메소드를 분리해 주는 것이 구조적으로 소스 코드를 파악하기 더 용이하다.@BeforeAll: 테스트 데이터 초기화Assertions (org,assertj.core.api)Assertions.assertThat(beforeInitialize).hasSize(0): 테스트를 검증하는 메소드 (의도한대로 동작을 했는지)'beforeInitialize'에서 받은 데이터의 사이즈를 체크'0' 이면 테스트를 통과, 그 외에는 테스트 실패// 테스트 데이터 초기화 @BeforeAll fun beforeAll() { println("----- 데이터 초기화 이전 조회 시작 -----") val beforeInitialize = experienceRepository.findAll() assertThat(beforeInitialize).hasSize(0) // 테스트를 검증하는 메소드 println("----- 데이터 초기화 이전 조회 종료 -----") println("----- 테스트 데이터 초기화 시작 -----") val experiences = mutableListOf<Experience>() for (i in 1..DATA_SIZE) { val experience = createExperience(i) experiences.add(experience) } experienceRepository.saveAll(experiences) println("----- 테스트 데이터 초기화 종료 -----") }@Test: 메소드를 테스트 메소드로 인식되게 함@Test fun testFindAll() { println("----- findAll 테스트 시작 -----") val experiences = experienceRepository.findAll() assertThat(experiences).hasSize(DATA_SIZE) println("experiences.size: ${experiences.size}") for (experience in experiences) { assertThat(experience.details).hasSize(experience.title.toInt()) println("experience.details.size: ${experience.details.size}") } println("----- findAll 테스트 종료 -----") 리포지토리 성능 개선JPQL의 fact join을 활용해 jpa에서 발생하는 n+문제를 해결하고, ProjectRepository와 ExperienceRepository의 성능을 개선.ExperienceRepositoryTest.kt의 fun testFindAllByIsActive() 실행11개의 쿼리가 실행됨 => jpa에서의 n+1 문제부모데이터 1번 조회 (결과: 10개) -> 각 자식데이터 조회 10번 JPA에서 proxy를 쓰는데, proxy는 가짜 객체이다.디테일을 바로 가져오는게 아닌 한번 랩핑된 가짜객체를 가지고 있고, 그 가짜 객체 안에 var details가 호출될 때 Query가 나가는 로직이 있음.=> 디테일 호출 -> 가짜 객체 호출 -> 가짜 객체에서 진짜 데이터를 안가지고 있으니, DB에서 쿼리를 가져옴근데 너무 비효율적임 => FetchJoin 활용ExperienceRepository.kt의 fun findAllByIsActive위에 @Query("select e from Experience e left join fetch e.details where e.isActive = :isActive") 달아줌- 'e' alias 별칭jpql: 자바의 객체지향적인 쿼리. sql과 비슷한데, 좀 더 객체의 관점에서 작성할 수 있는 sql. JPA에서 JPQL을 가지고 실제로 DBMS에 맞는 쿼리로 바꿔서 DB로 쿼리를 보냄 (ex. @Query)쿼리가 한개만 나갔다.ProjectRepository.ktprojectSkill, projectDetail과 관계를 맺고 있다.패치조인의 단점, 한계점이 위와 같이 여러 개의 엔티티와 관계를 맺고 있을 때, 이것들을 한꺼번에 조회할 수 없다.=> 네이티브 쿼리로 풀거나, 쿼리 DSL or something=> yml의 default_batch_fetch_size: 10 을 통해 어느정도의 성능 문제를 해결 => n+1의 완전히 해결하는 것이 아닌 fetchSize의 값에 따라, m의 팻치사이즈가 n번 나가는 쿼리를 n/m으로 줄여준다.

백엔드SpringBootKotlinWeb

인프런 워밍업 클럽 백엔드 2주차 후기

2주차 후기 강의를 듣고 발자국 남기는걸 까먹어서 부랴부랴 남긴다 ㅎㅎ..😅1주차에 이어서 남은 리팩토링 강의를 들으면서 강사님께서 남겨주신 리팩토링 연습문제를 풀어보았다.인프런의 스타강사 영한님의 명언 "백문이 불여일타"를 기억하기에 모든 강의를 열심히 따라 치다가, 이번엔 스스로 문제풀이를 해보려고 하니 생각보다 어려웠다.정리한 강의 내용을 쥐어짜며, 어떻게든 내 나름대로 추상화와 코드정리를 해본 후 강사님과 비교해보았다.나와 비슷하게 하신 점을 볼 때는 뿌듯했고, 내가 미처 생각치 못한 방식으로 리팩터링을 하실 때는 감탄을 금치 못했다.강의 너머 우빈님께 정말 많은 것을 배워볼 수 있는 기회였다.그리고 정말 코드에선 추상이라는 개념은 중요한 것 같다. 동등한 추상 레벨을 맞추거나, 메시지 이름을 통해서 "아 이거는 이런 역할을 하겠구나" 의미를 파악할 수 있는 점들이 중요함을 느꼈다. 하지만 또 모든 것을 추상해버리려는 오버 엔지니어링도 주의해야겠다. 이런 경각심을 배우며 실전에서도 조심하며 해볼 수 있도록 해야겠다.이제 남은 테스트 코드 강의를 들으며 더 정진해야겠다! 아래는 강의를 정리한 내용섹션6 - 리팩토링 연습문제: 스터디 카페 이용권 선택 시스템사용자는 시간권, 주단위 이용권, 1인 고정석 중 선택할 수 있음시간권: 2, 4, 6, 8, 10, 12시간주권: 1, 2, 3, 4, 12주고정석: 4주, 12주추가금액을 내면 사물함도 사용할 수 있는데, 고정석인 경우만 선택 가능함선택한 이용권, 사물함 여부에 따른 최종 금액을 계산해준다.오픈 이벤트로 2주권 이상 10%, 12주권 15% 할인을 진행중임리팩토링 포인트추상화 레벨객체로 묶어볼만한 것은 없는지객체지향 패러다임에 맞게 객체들이 상호 협력하고 있는지SRP: 책임에 따라 응집도 있게 객체가 잘 나뉘어져 있는지DIP: 의존관계 역전을 적용할만한 곳은 없는지일급 컬렉션리팩토링(2) - 객체의 책임과 응집도IO 통합일급 컬렉션display()의 책임Order 객체리팩토링(3) - 관점의 차이로 달라지는 추상화FileHandler를 바라보는 관점헥사고날 아키텍처 - 포트와 어댑터포트: 인터페이스: 규격만 맞으면 꽂을 수 있는 플러그 같은 스펙어댑터: 포트에 맞는 구현체섹션7 - 기억하면 좋은 조언들1. 능동적 읽기복잡하거나 엉망인 코드를 읽고 이해하려 할 때, 리팩토링하면서 읽기공백으로 단락 구분하기메서드와 객체로 추상화 해보기주석으로 이해한 내용 표기하며 읽기우리에게는 언제든 돌아갈 수 있는 git reset —hard가 있다.핵심 목표는 우리의 도메인 지식을 늘리는 것. 그리고 이전 작성자의 의도를 파악하는 것2. 오버 엔지니어링필요한 적정 수준보다 더 높은 수준의 엔지니어링을 하는 것EX) 구현체가 하나인 인터페이스인터페이스 형태가 아키텍처 이해에 도움을 주거나, 근시일 내에 구현체가 추가될 가능성이 높다면 OK구현체를 수정할 때마다 인터페이스도 수정해야 함코드 탐색에 영향을 줌. 애플리케이션이 비대해 짐EX) 너무 이른 추상화정보가 숨겨지기 때문에 복잡도가 높아진다.후대 개발자들이 선대의 의도를 파악하기가 어렵다.3. 은탄환은 없다만능 해결사 같은 기술은 없다 클린 코드도 은탄환이 아니다실무: 2가지 사이의 줄다리기지속 가능한 소프트웨어의 품질 VS 기술 부채를 안고 가는 빠른 결과물대부분의 회사는 돈을 벌고 성장해야 하고, 시장에서 빠르게 살아남는 것이 목표임이런 경우에도, 클린 코드를 추구하지 말라는 것이 아니라, 미래 시점에 잘 고치도록 할 수 있는 코드 센스가 필요함결국은, 클린 코드의 사고법을 기반으로 결정하는 것 기술부채: 당장 직면한 문제를 적정한 기술을 도입하지 못하고 단순하게 해결하고 미래에 미뤄두는 것모든 기술과 방법론은 적정 기술의 범위 내에서 사용되어야 한다.EX) 당장 급하게 배포 나가야 하는데, 동료에게 style 관련된 리뷰를 주고 고치도록 강요하는 사람도구라는 것은, 일단 그것을 한계까지 사용할 줄 아는 사람이 그것을 사용하지 말아야 할 때도 아는 법이다.적정 수준을 알기 위해, 때로는 극단적으로 시도해보자 (오버엔지니어링도 해보면서 적정 수준이 무엇일지 겪어보기)

백엔드

river_bori

인프런 워밍업 클럽 2기 - 백엔드 프로젝트 (Spring, Kotlin) 1주차 발자국

일주일간 학습한 내용 요약웹 개발 기본과 프로젝트 준비웹 서비스를 구성하는 요소클라이언트(브라우저, 서버컴 중 요청쪽 등) - 서버(응답주체, CRUD작업, 서버컴퓨터 집합인 클러스터를 구성) - DB(DBMS)DBMS 서버의 IP주소는 인터넷 세팅을 할때 등록해서 컴퓨터에 알려줘야 한다.웹 프레임워크와 Spring웹 프레임워크: 동적 웹 서비스 개발을 편리하게 만들어주는 도구백엔드 : (Java, Kotlin - Spring), (JavaScript, TypeScript - Express.js, Nest.js), (Python - Django), (Rudy - Rudy On Rails)프론트엔드 : (JavaScript - React(라이브러리), Angular, Vue.js) 프레임워크 vs 라이브러리 -> 제어의 주도권 차이프레임워크: DIY 가구 키트 - 사용자가 틀 안에서 주어진 것을 활용하여 원하는 것을 만드는 것라이브러리: 공구 상자 - 사용자가 주도권을 가지고 원하는 것을 만듦Spring Framework: Java기반의 웹 프레임워크, 웹 서버 개발의 상당 부분을 편리하게 모듈화 해놓음MVC 패턴: 요청 처리, 데이터, 화면 간의 결합도를 낮춰 유지보수 용이하게 함View: 사용자와 상호작용Controller: 요청받아 작업을 수행Model: 데이터 담는다. View는 데이터를 꺼내고, Controller는 데이터를 넣는다. 디자인 패턴? 경험적으로 특정 문제 상황을 해결하기에 최적이라고 생각되는 설계, 방법론.코드의 가독성, 유지보수성, 결합도, 응집도 등을 고민 하며 최적화된 코드를 작성하는 자세가 중요레이어드 아키텍처: 데이터를 컨트롤러에서 받아서 모델에 넣기 전까지 처리하는 과정을 이해하기 쉽고 관리하기 쉽게 구조화하는 방법 중 하나. 입력받은 데이터에 이상은 없는지 검증하고, 한번 가공해서 받은 데이터 기반으로 다른 데이터를 조회하는 등을 할 수 있다.MVC의 컨트롤러와 분리Controller(Presentation) - Service(Business) - Repository(Data Access) 각 레이어 별 역할 분리컨트롤러: 사용자와 상호작용서비스: 주요 로직 처리레파지토리: DB와의 상호작용스프링 Bean과 의존성 주입(Dependency Injection)스프링Bean: 스프링에서 관리하는 인스턴스어플리케이션 동작에 필요한 클래스들을 개발자가 직접 만들지 않고 스프링이 만들어서 관리하도록 위임한다. "제어의 역전 IoC(Inversion of Control)" 제어의 주체가 개발자가 아닌 프레임 워크가 되었다.스프링부트에서는 어노테이션으로 간단하게 스프링 빈을 정의해서 스프링이 그 인스턴스를 만들게 할 수 있다. (컨트롤러,서비스,레파지토리,컴포넌트 등의 어노테이션)스프링 실행 - 컴포넌트 스캔 - 클래스들을 하나하나 살피면서 어노테이션이 있으면 Bean으로 만듦(인스턴스를 만들어 빈으로 등록)의존성 주입(DI)는 역제어(IoC)의 한 형태레퍼지토리를 서비스에 넣어주고, 서비스를 컨트롤러에 넣어주고..의존성 주입"주입"은 의존성(서비스)를 사용하려는 객체(클라이언트)로 전달하는 것. 어떤 서비스를 호출하려는 클라이언트는 그 서비스가 어떻게 구성되었는지 알지 못해야 한다.의존성 주입의 의도는 객체의 생성과 사용의 관심을 분리하는 것. 이는 가독성과 코드 재사용을 높혀줌.클래스는 더 이상 객체 생성에 대한 책임이 없음.클라이언트의 생성에 대한 의존성을 클라이언트의 행위로부터 분리"매개변수 전달"과 동일하게 동작한다. 주입으로써 "파라미터 전달"은 클라이언트를 세부 사항과 분리하기 위해 수행되고 있는 부가적인 의미를 전달의존성 주입은 네 가지 역할을 담당하는 객체 및 인터페이스를 전제로 한다.사용될 서비스 객체사용하는 서비스에 의존하는 클라이언트 객체클라이언트의 서비스 사용 방법을 정의하는 인터페이스서비스를 생성하고 클라이언트로 주입하는 책임을 갖는 주입자비유하자면,서비스 - 전기, 가스, 하이브리드 또는 디젤 자동차클라이언트 - 엔진에 상관 없이 동일하게 차를 사용하는 운전자인터페이스 - 운전자가 기어와 같은 엔진의 세부 사항을 이해할 필요가 없도록 보장해주는 자동변속기주입자 - 아이에게 어떤 차를 사줄지 결정하고 구매해준 부모사용될 수 있는 모든 객체는 서비스로 여겨진다. 다른 객체를 사용하는 모든 객체는 클라이언트로 여겨진다.[위키백과]생성자(costructor) 주입수정자(setter) 주입필드(Field) 주입추가 공부 필요 - 이해가 안됨생성자 주입 방식을 권장의존성이 바뀌는 것을 방지할 수 있다순환참조 시 컴파일 오류가 발생해, 런타임 단계에서의 메소드가 서로 호출하는 스택오버플로우 에러 방지한다.의존하는 빈이 누락되면 컴파일 오류가 난다.HTTP와 REST APIHTTP: 네트워크로 통신하는 두 컴포넌트 간 통신규약HTTP 요청/응답Request: Start Line(HTTP 메서드, URL, HTTP 버전), Header(키/밸류 형태 메타데이터, 컨텐츠의 길이/유형, 클라이언트 정보), Body(본문, JSON 포맷)Response: Start Line(HTTP 상태코드 - 아래 있음)HTTP 요청메서드GET: 서버 자원을 조회하고 가져온다. 브라우저 주소창은 항상 GET 메서드로 요청,POST: 리소스를 생성메소드(CREATE)PUT, PATCH, DELETEHTTP 상태 코드200 OK: 요청이 정상처리됨300 Multiple Choices(Redirection): 사파리에서 네이버 입력 시, 네이버 서버가 300번대 코드와 m.naver... 주소를 주면 사파리가 다시 m.naver...으로 요청하고 m.naver..서버가 200번대 코드와 html 파일을 사파리에 준다 400 Bad Request: 클라이언트의 오류500 Internal Server Error: 서버의 오류(DB 다운 등)--> 응답코드는 서버 개발자가 정한다.Http는 규약이지만 자유도가 높은 규약이다.REST API: URL도 개발자가 정하기 나름이지만, 일종의 표준처럼 사용하는 아키텍처(기억 장치의 주소 방식)로 REST API를 사용한다.* 아키텍처: 기능 면에서 본 컴퓨터의 구성 방식. 기억 장치의 주소 방식, 입출력 장치의 채널 구조 따위REST 원칙을 알면 새로운 API를 봐도 어떤 API인지 직관적인 추측이 가능해진다. -> 개발자 간의 커뮤니케이션 비용이 줄어든다.REST API의 핵심URL 이용한 자원 표현: URL만 보고 어떤 요청인지 이해 가능HTTP 메서드 이용한 행위의 표현: 의미에 맞는 적절한 메서드 사용HATEOAS(헤이티어스) 준수: 응답에 링크 포함해 클라이언트의 다음 행동을 가이드 클라이언트에서 서버로 데이터 전달 방법Query Parameter: URL에 있다. ((get) inflearn.com/roadmaps?terms=5&page=1) -->'?'뒤 내용이 쿼리파라미터이다.HTTP Request Body: Http 메시지 안에 들어있기 때문에 Post등을 쓰지 않는 이상 눈으로 보기 어렵다.Path Variable(경로변수): URL에 있다.코딩 컨벤션: 코딩을 하는 프로그래머 사이의 규칙 규약, 읽고 관리하기 쉬운 코드 작성을 위한 코딩 스타일 규약데이터베이스란DBMS: 데이터를 체계적으로 관리하기 위한 프로그램관계형/비관계형 데이터베이스로 나뉜다관계형 DB(RDBMS): 행과 열로 이루어진 표의 형태후보키: 유일성(중복x) 최소성(최소한의 컬럼조합 사용)기본키PK: 후보키 중 하나학과와 학생은 1:N의 관계복수전공시학생 join 학과를 하면 학번의 유일성이 깨진다때문에 중간에 맵핑 역할을 하는 테이블 필요 "학생전공"테이블 학생전공ID(기본키)학과와 학생이 N:M 관계 가능해짐학생전공 join 학생 join 학과DB는 프로젝트의 골격과도 같기 때문에 서비스 운영 중 DB구조를 바꾸는 것은 매우 어렵다. 프로젝트 설계가 중요하다.오라클(유료), MySQL(유/무료),PostgreSQL(꾸준히 점유율 올라가는 오픈소스DB, 플러그인 확장성이 좋다.)비관계형 DB: 관계형DB를 제외한 모든 종류의 DB(키-값형, 문서형 등)개발하려는 서비스에 따라 비관계형DB도 공부 필요MongoDB: 문서형, 데이터를 비정형적으로 저장가능Redis: 키-값, 주로 캐시 용도로 사용(자주 조회된 데이터를 레디스에 저장해서 사용-빠르다)  JPA란JAP: Java Persistence API, 자바 ORM 기술의 표준 인터페이스, Java의 객체를 관계형DB의 테이블로 또는 그 반대로 변환해주는 기능/맵핑을 해주는 라이브러리서비스를 좀더 객체지향적인 관점에서 설계 및 유지보수할 수 있게 도와주고개발자가 직접 작성해야하는 코드를 줄여준다.ORM: Object Relational Mapping, 객체 관계 매핑, 객체지향 프로그래밍의 인스턴스와 관계형DB를 매핑해주는 기술학생, 학과 테이블 -> 클래스 (class Project)각 컬럼들 -> 클래스에 있는 필드 (val title)각 레코드 -> 클래스로 만들어진 인스턴스여러 레코드 -> 클래스의 리스트 (List<ExperienceDetail>)이렇게 테이블에 매핑되는 자바 클래스를 엔티티라고 한다. (class Project)DB엔티티에 해당하는 자바 클래스 = JPA 엔티티ORM은 테이블들이 갖는 관계까지 이용해 DB를 좀 더 쉽게 다룰 수 있는 기능을 제공ORM의 장점원래 자바로 DB를 다루려면 SQL문을 개발자가 직접 작성해서 DB로 보내야한다.JPA는 개발자가 정의해둔 엔티티의 필드들과 연관관계를 통해서 테이블 구조를 이해하고 있고 이를 바탕으로 대신 SQL을 작성함 => 개발 생산성 증가엔티티라고 부르는 Java 객체를 기반으로 쿼리 작성하기 때문에 데이터를 객체지향적으로 관점에서 접근 가능스프링과 구체적인 RDBMS(오라클, MySQL 등)에 대한 종속성을 끊어 의존성이 줄어든다. -> DB를 쉽게 변경 가능하다(ex. 오라클에서 MySQL로) ORM의 단점충분한 학습이 없으면 의도와 다르게 쿼리가 동작할 수 있음 ex: (n+1) 직접 쿼리를 작성했을 때는 한번만 쿼리가 전송됨, JPA를 사용하면 쿼리가 여러번 전송됨. => 해결법은 있지만 별도로 공부가 필요간단한 쿼리작성에는 효과적, 복잡한 쿼리에서는 한계점이 있어 불가피하게 구체적인 RDBMS에 종송적인 네이티브 쿼리 작성 => 의존성을 줄이는 장점이 사라짐트랜잭션: 데이터베이스의 개념, 여러 DB 작업을 하나로 묶는 논리적 단위(계좌이체 예시)커밋: 트랜잭션으로 묶인 모든 작업을 DB에 영구히 반영하는 작업롤백: 트랜잭션으로 묶인 모든 작업을 원상복구 하는 작업영속성 컨텍스트: 어플리케이션의 로직과 DB 사이에 있는 임시 메모리 또는 버퍼 공간개발자가 java코드로 CRUD 명령을 수행할 때 JPA는 이 영속성 컨텍스트를 거쳐 DB와 상호작용트랜잭션과 주기가 같다. 하나의 트랜잭션의 DB 작업들을 영속성 컨텍스트를 거쳐 좀 더 효율적으로 처리 영속성 컨텍스트 처리과정데이터 조회시 1차 캐시라는 곳에서 해당 데이터가 있는지 먼저 확인 -> 데이터 없으면 DB로 조회쿼리 날림1차 캐시(엔티티1)에 조회한 데이터를 저장 -> 엔티티1 상태를 스냅샷 저장그 후 또 같은 엔티티 조회시 DB까지 쿼리가 날라가지 않고 1차 캐시에 있는 데이터를 그대로 가져감(쿼리 날리는 과정이 생략)영속성 컨텍스트 더티체킹(변경감지)데이터 처음 조회 시 처음 상태를 스냅샷 저장 후 영속성 컨텍스트가 종료될 때 현재 있는 엔티티1와 엔티티1스냅샷을 비교두개가 다를 경우 업데이트가 있다고 JPA가 판단하고 알아서 쿼리를 작성해 DB에 전송함.영속성 컨텍스트 쓰기 지원한 트랜잭션 안에서 for문을 가지고 1~100까지 데이터를 insert할 때,for문 반복이 수행될 때마다 DB로 insert 쿼리를 전송하지 않고, 영속성 컨텍스트에 저장할 데이터를 넣어 뒀다가 트랜잭션 종료될 시점에 한번에 모든 쿼리를 날림때문에 JPA로 insert를 날린 시점과 log에서 insert, update 쿼리가 찍히는 시점이 차이가 날 수 있다.영속성 컨텍스트 플러시(Flush)개발자가 원하는 시점에 DB에 쿼리 전송 가능패키지 구조[패키지 구조]패키지: 연관된 자바파일들을 묶어주는 디렉토리(폴더)다른 개발자들이 프로젝트 구조를 이해하는데 도움이 된다다양한 방법론이 있다.presentation(방문자), admin(관리자), domain(프로젝트가 공통으로 가진 뼈대(코어)가 되는 기능들이 모인 패키지, DB 접근 기능 등)도메인 패키지가 베이스가 되고 그 위에 방문자, 관리자 패키지를 병렬로 쌍은 느낌의 구조 각 패키지의 하위 패키지 => 다양한 방법론 중 하나의 구조다.domain: 4개의 하위 패키지configuration(암호와 관련된 설정 클래스 들어갈 패키지),constant(도메인 패키지에 사용되는 상수들),entity(DB의 테이블에 대응하는 java객체들이 들어갈 패키지) ,repository(각 엔티티 별 DB에서 CRUD를 수행할 객체들)presentation:  5개의 하위 패키지controller,dto(화면에서 필요로 하는 데이터를 담는 클래스),interceptor(컨트롤러까지 요청이 들어가기 전에 그 모든 컨트롤러에 대해 공통 처리를 해주는 클래스, 인터셉터에서 각 화면을 조회할 때 그 요청에 대한 정보를 따로 DB에 저장하는 기능 개발),repository(DB와 직접 상호작용을 하지 않고 도메인의 레퍼지토리 패키지를 활용해서 프레젠테이션 레이어에서 필요로 하는 DB작업을 쉽게 할 수 있도록 중간에 랩핑 해주는 기능, 디자인패턴 중 Facade 패턴),=> 퍼사드(Facade) 패턴: 복잡한 시스템을 보다 쉽게 사용 가능하도록 단순화된 인터페이스를 제공. 시스템의 복잡성 숨기고, 사용자 친화정 인터페이스로 시스템 접근을 용이하게 함.service=> 도메인과 프레젠테이션 패키지를 결합한 것도 하나의 프로젝트로 볼 수 있다. 나중에 확장 될때, 도메인과 어드민을 또 따로 결합해 사용할 수 있도록 만들어진 구성 admin:advice(컨트롤러 어드바이스, 예외를 처리하는 클래스),context(데이터베이스에 만들 테이블들 별로 context 패키지 안에 하위 패키지들이 있다.각 테이블 별로 화면 조회부터 데이터 추가/수정/삭제 기능들을 분리해 넣을 예정ex) achievement(이 안에 각각 controller, service=view,form 하위 패키지가 또 있다), dashboard, experience, ...etc),=> 새로운 테이블이 추가되고 관리할 기능이 필요하다면, 컨텍스트 하위에다가 새로운 패키지를 만들어 기존 기능에 대해 영향이 없이 확장성있게 개발 가능하게 구성됨data(API 공통 응답 포맷, 입력 폼 정보, 테이블 정보 등을 담는 클래스),exception(어드민 레이어에서 따로 커스터마이징한 예외들 들어갈 패키지),interceptor(화면에서 보여지는 사이드바를 초기화 하는 클래스),security(로그인 관련된 기능들)테이블 설계(명세)메인페이지(=index page) 사용 테이블introduction 테이블 (소개글 등)created_date_time, updated_date_time 컬럼은 메타 데이터 타입으로 데이터의 생성과 수정을 추적하기 위한 목적이다.link 테이블 (링크 아이콘 등)name을 기반으로 이미지 Resume 페이지 사용 테이블experience(학력, 경력, 한줄 요약 등)experience_detail(어떤 경험에 대한 내용인지, 설명 등)=> experience와 experience_detail은 1:N으로 묶인 테이블achievement(수상, 자격증 등)skill(사용할 줄 아는 기술들)Projects 페이지 사용 테이블projectproject_detail(url 컬럼이 추가로 있다)project_skill(프로젝트와 skill을 맵핑해주는 테이블)=> 프로젝트와 스킬은 N:M의 관계기타http_interface(사용자가 페이지 조회를 한번 할 때마다 사용자의 브라우저에서 서버로 요청이 들어올 때 요청의 정보를 저장하는 테이블.조회수, 일자별 조회수 통계, ip주소를 통한 중복방문제거, 모바일/데스크탑 방문 통계 등이 가능하다)=> interceptor를 사용해 보기 위해 넣었다.개발 환경 구성윈도우 사용자는 이 강의를 스킵하고 강의자료로 진행Homebrew(Mac Only)JDK(zulu): 버전 17로IntelliJ IDEAH2 DatabaseDBeaverDocker

백엔드SpringBootKotlinWeb

Taeho

인프런 워밍업 클럽 - CS Day 12

AlgorithmMerge Sort재귀로 구현하는 정렬 알고리즘Divide and Conquer복잡한 문제를 더 작고 관리하기 쉬운 하위 문제들로 나누어 해결하는 알고리즘 설계 기법구현 방법분할(Divide): 정렬할 배열을 거의 같은 크기의 두 부분 배열로 나눈다.정복(Conquer): 각 부분 배열을 재귀적으로 정렬한다.결합(Combine): 정렬된 부분 배열들을 하나의 정렬된 배열로 병합한다.1 ~ 3을 부분 배열의 크기가 1이 될 때까지 반복한다.시간 복잡도분할 과정: 배열을 반으로 나누는 과정이 log n번 일어납니다.병합 과정: 각 단계에서 n개의 요소를 비교하고 병합합니다.→ 전체 시간 복잡도 : O(n) * O(log n) = O(n log n)장점속도가 빠르다.시간 복잡도 : O(n log n)단점병합 과정에서 임시 배열이 필요하여 추가적인 메모리 공간을 사용한다.이해와 구현이 복잡하다.OS지역성 이론(90 : 10 법칙)프로그램이 실행될 때 90%의 시간이 10%의 코드에서 소요된다.조만간 쓸 데이터만 메모리로 올리고 당분간 필요하지 않을 것 같은 데이터는 스왑영역으로 보내 성능을 향상시킨다.디맨드 페이징필요할 것 같은 데이터를 메모리로 가져오고, 쓰이지 않을 것 같은 데이터는 스왑영역으로 이동시키는 정책지역성 이론을 구현한 정책메모리에서 데이터를 가져오는 정책Swap in스왑 영역에서 물리 메모리로 데이터를 가져오는 과정Swap out물리 메모리에서 스왑 영역으로 데이터를 보내는 과정Page Table Entry(PTE)페이지 테이블을 이루고 있는 행접근 비트페이지가 메모리에 올라온 후 데이터에 접근이 있었는지 알려주는 비트메모리에 읽기나 실행 작업을 했다면 1로 변경된다.변경 비트페이지가 메모리에 올라온 후 데이터에 변경이 있었는지 알려주는 비트메모리에 쓰기 작업을 했다면 1로 변경된다.유효 비트페이지가 물리 메모리에 있는지 알려주는 비트유효비트 1 : 페이지가 스왑영역에 있음유효비트 0 : 페이지가 물리 메모리에 있음읽기/쓰기/실행 비트(권한 비트)해당 메모리에 접근권한이 있는지 검사하는 비트Page Fault프로세스가 가상 메모리에 접근요청을 했을 때 MMU(메모리 관리자)는 페이지 테이블을 보고 물리 메모리의 프레임을 찾아낸다.물리 메모리에 프레임이 없다면 MMU는 Page Fault라는 인터럽트를 발생시킨다.Page Fault가 발생하면 스왑영역에 접근하고 해당 프로세스는 대기 상태가 된다.→ 스왑 영역의 데이터가 메모리로 올라가는 작업을 수행한다.→ 데이터가 물리 메모리로 올라가는 경우 해당 프로세스가 실행 상태로 변경된다.공간의 지역성현재 위치와 가까운 데이터에 접근할 확률이 높다.시간의 지역성최근 접근했던 데이터가 오래 전에 접근했던 데이터보다 접근할 확률이 높다.페이지 교체 정책메모리가 꽉찼을 때 어떤 페이지를 스왑영역으로 보낼지 결정하는 정책무작위로 교체하는 방법지역성을 고려하지 않는다.자주 사용되는 페이지가 선택될 수 있어 성능이 좋지 않다.→ 거의 사용되지 않는다.FIFO(First-In First-Out)메모리에 들어온지 가장 오래된 페이지를 교체한다.→ 자주 사용하는 페이지가 교체될 수 있다.구현이 간단하고 성능이 나쁘지 않아 변형해서 사용된다.H/W 적으로 접근비트를 지원하지 않는다면 FIFO를 사용해야 한다.Belady's AnomalyPage Fault를 줄이려고 메모리를 더 늘려서 프레임의 수를 늘렸는데 오히려 Page Fault가 더 많이 발생하는 현상FIFO에서만 발생한다.2차 기회 페이지 교체 알고리즘FIFO 방식에서 자주 사용하는 페이지에게는 또 한번의 기회를 준다.FIFO와 동일하게 동작하지만 Page Fault 없이 페이지 접근에 성공했다면 해당 페이지를 큐의 맨 뒤로 이동시켜 수명을 연장시켜 준다.Optimum앞으로 가장 오랫동안 사용되지 않을 페이지를 교체한다.→ 이후에 어떤 요청이 들어올지 알고 있어야 한다.→ 사실상 구현이 불가능한 이론적인 방법다른 알고리즘과 성능 비교를 할 때 참고용으로 사용된다.LRU(Least Recently Used)시간의 지역성에 따르면 최근 사용한 데이터가 앞으로도 사용될 확률이 높기 때문에 최근에 가장 사용을 적게한 페이지가 앞으로도 사용될 확률이 적다.Optimum 알고리즘에 근접한 성능을 갖는다.프로그램이 지역성을 띄지 않을 때는 성능이 떨어진다.PTE에 시간을 기록하려면 비트가 많이 필요하다.→ 비트를 많이 사용하는 것은 어렵기 때문에 접근비트를 이용하여 LRU에 근접하게 구현한다.Clock Algorithm접근 비트 하나만을 사용하여 구현하는 알고리즘일정 시간 간격마다 모든 페이지의 접근 비트를 0으로 초기화한다.페이지가 참조되는 경우 1로 설정된다.⇒ 일정 시간 간격마다 페이지가 참조되었는지 참조되지 않았는지 알 수 있다.페이지를 원형으로 연결한다.Page Fault가 발생해서 스왑영역으로 보내야 하는 경우클락 핸드는 현재 참조하고 있는 접근 비트를 본다.해당 비트가 1인 경우 해당 비트를 0으로 변경하고 다음 비트를 바라본다.접근 비트가 0인 페이지를 발견하면 해당 페이지를 스왑 영역으로 보낸다.Clock Hand스왑영역으로 옮길 페이지를 가르키는 포인터시계 방향으로 돈다.Enhanced Clock Algorithm접근 비트만 이용하는 것이 아니라 변경 비트도 함께 사용하는 알고리즘스왑 영역으로 보내지는 가장 우선순위접근 비트 : 0, 변경 비트 : 0접근 비트 : 0, 변경 비트 : 1접근 비트 : 1, 변경 비트 : 0접근 비트 : 1, 변경 비트 : 1스레싱프로세스들이 실제 실행되는 시간보다 페이지를 교체하는 데에 더 많은 시간을 사용하고 있는 현상⇒ 페이지 부재(page fault)가 과도하게 발생하여 CPU 이용률이 급격히 떨어지는 현상다중 프로그래밍의 정도가 높아져 각 프로세스에 할당된 메모리가 부족해지면서 발생CPU 이용률 저하 → 운영체제의 다중 프로그래밍 증가 → 페이지 부재 증가의 악순환이 반복근본적인 원인 : 물리 메모리의 크기가 부족한 것→ 메모리의 크기를 늘려서 해결(H/W 수준)할 수 있다.S/W 수준의 해결 방법프로세스가 실행되면 일정량의 페이지를 할당한다.→ Page Fault가 빈번하게 발생하는 경우 : 더 많은 용량의 페이지를 할당한다.→ Page Fault가 거의 없는 경우 : 적은 용량의 페이지를 할당한다.워킹셋프로세스가 일정 시간 동안 자주 참조하는 페이지들의 집합프로그램의 지역성(Locality) 특성을 이용한 개념자주 참조되는 워킹셋을 주기억장치에 유지함으로써 페이지 부재와 교체를 줄인다.시간에 따라 자주 참조하는 페이지들의 집합이 변화하므로 워킹셋도 동적으로 변한다.컨텍스트 스위칭에서 사용된다.

알고리즘 · 자료구조워밍업클럽CS전공지식DAY12

이호준

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

강의 수강 2주차에는 자바스크립트 섹션 7. 비동기부터 리엑트 섹션4 todo 최적화 tailwind css부분까지 수강 했습니다. 공부방식은 1주차와 비슷하게 강의 자료를 먼저 보고 동영상 강의를 수강 했습니다.JavaScript비동기Promise를 async 함수로 변형해 보는 코드를 실습해 보았습니다.async와 await도 똑같이 Promise로 동작한다는 사실을 알 수 있었고, 아무래도 .then() 메서드를 체이닝하는 것 보단 async awit 코드가 보기에 훨씬 좋아 보였습니다.Symbol, Iterator, GeneratorSymbol을 이용해 unique한 인덱스 값을 사용할 때 필요할 것으로 생각 됩니다Iterator는 Iterable한 객체를 Symbol.Iterator를 .next() 메서드를 이용해 value와 done을 값을 가진 객체를 반환해 줍니다.Generator는 function에 *을 붙이고 return 대신 yeild를 붙여 사용합니다. Generator를 사용하면 개발자가 원하는 시점에 함수를 사용하고 정지 시킬 수 있을 것으로 생각 됩니다.아직까진 Symbol, Iterator, Generator를 직접 사용해보면서 코드를 짜보지 않아 어디서 유용하게 사용할 수 있을지는 직접 와닫지는 않았습니다.디자인 패턴[디자인 패턴 실습](https://github.com/hojun-lee99/inflearn-warmingup-fe/tree/main/Javascript/study/design-patterns)디자인 패턴은 강의에 있던 factory, singletone, mediator, observer, 패턴을 실습해 보았습니다.factory 패턴 - 동일한 속성을 가진 객체들을 만들 때, factory 함수를 만들어 객체를 생성한다.singletone 패턴 - 클래스에서 하나의 인스턴스만 생성하게 하여, 여러 곳에서 인스턴스를 공유하여 사용하는 방식 ex) a가 볼륨을 50에서 20으로 조정 했을때 다른 곳에서 사용하고 있던 b의 볼륨도 20으로 조정되면 싱글톤 패턴으로 작성한다.mediator 패턴 - 객체 그룹들을 관리하는 중재자를 만들어 중재자에 중앙 관리 권하는 주는 방법, ex)채팅방에서 메시지를 보내면 중재자가 메시지를 받아 제어하고 받는 사람에게 메시지를 보내준다.observer 패턴 - observer들이 등록한 subject에 변경 사항이 있으면 알림을 보내주고 등록을 취소하면 알림을 보내주지 않는다.프로젝트 만들기[프로젝트 실습](https://github.com/hojun-lee99/inflearn-warmingup-fe/tree/main/Javascript/study/making-project)스탑워치 앱setInterval과 clearInterval 메서드의 사용법에 대해서 실습해볼 수 있었습니다.Todo 앱todo 앱은 이전에 혼자서 한번 만들어 보았는데 저번과 차이점은 수정 버튼이 추가 되었습니다.input이 생성 되면 focus 처리 해주는 것과 focus가 해제 되면 blur 이벤트가 발생한다는 부분을 알 수 있었습니다.액셀 앱- 다른 부분은 앞선 프로젝트와 비슷했지만 export 버튼 클릭 이벤트 부분은 새로 배운 부분입니다.filter를 이용해 헤더를 제외하고 map을 이용해 cell 안에 데이터만 추출하고 join을 이용해 `,`을 붙여주는 체이닝을 이렇게 사용해 문자열을 처리하는 것을 배울 수 있었습니다.웹에서 데이터를 파일로 변환에주기 위해선 Blob 객체를 이용하는 것도 배울 수 있었습니다.React리액트 동작원리 SPA(single page application)리엑트는 index.html 싱글 페이지 만을 사용하며, 이를 위해 HTML5의 history API를 이용한다. 가상 DOM을 사용하여 이전 가상DOM에서 변경된 부분만 확인 Diffing 하여 실제 DOM의 변경됨 노드만을 교체해준다.웹팩(webpack)웹팩은 여러개의 모듈과 같은 파일들을 압축하여 최적화된 자바스크립트 파일로 만들어주어 라이브러이이다.바벨(babel)바벨은 작성한 최신 코드들을 지원하지 않는 브라우저에서 코드가 작동할 수있게 이전 코드로 변환해주는 라이브러리이다.React state데이터가 변경 될때 리엑트가 리렌더링 되기 위해선 state를 이용해야 합니다. // changeValue를 사용할때 마다 React가 화면을 re-rendering state = { value: ''; }; changeValue = (data) => { this.setState({ value: data}); }; //state에 있는 value값 let stateValue = this.state.value;React Hooksclass없이 state를 사용할 수 있어 class 컴포넌트 대신 함수형 컴포넌트로 작성 할 수 있는 기능 입니다. tailwind CSStailwind는 미리 정의된 스타일 클래스가 존재해 이를 이용해 스타일을 작성할 수 있습니다.프로젝트프로젝트는 4번 책 리스트 앱과 5번 github finder앱을 작성 완료 했습니다.[책 리스트 앱](https://github.com/hojun-lee99/inflearn-warmingup-fe/tree/main/Javascript/quiz04)책 리스트 앱은 todo앱과 기능상으로 큰 차이점이 있지 않아 작성하는데 큰 어려움은 없었던 것 같습니다.todo앱과 똑같이 locatStorage를 이용하여 CURD 기능을 구현했습니다.  [github finder 앱](https://github.com/hojun-lee99/inflearn-warmingup-fe/tree/main/Javascript/quiz05)Github Finder 앱은 API를 처음 사용해봤기 때문에 그부분에서 많은 시간이 걸렸습니다.Postman을 이용해 api.github.com에 GET 요청을 보내 보면서 API 사용법을 익혔습니다.API request는 비동기이기 때문에, async await를 이용해보면서 비동기 코드를 어떻게 작성하는지 익힐 수 있었습니다.octokit과 토큰은 Github Docs를 참고했고 key.js를 gitignore에 추가해 토큰이 유출 되지 않게 관리 했습니다.getUserInfo에서 받아온 값을 사용하기 위해선 await 필요2주차 회고api 요청 받고고 데이터를 처리하는 부분을 공부하는데 생각보다 많은 시간이 소요되었습니다. github finder 앱을 만드는데 많은 시간이 소요 돼 2주차 프로젝트는 github finder앱까지 만들 수 있었습니다. 그래도, 새로 공부한 부분을 제대로 작동되는 앱을 만들었다는 사실이 만족감을 많이 주었습니다.강의 진행 속도도 현재 스켸줄 보다 느리기 때문에 다음 3주차 완주까지 최선을 다해서 진행 해보도록 하겠습니다!

프론트엔드

김예지

[인프런 워밍업 스터디 클럽 2기 FE] 3주차 과제 - 포켓몬 도감 앱 만들기

포켓몬 도감 앱github : 10-pokeapi 기능목록PokéAPI를 사용하여 포켓몬 목록과 세부 정보를 요청하고 화면에 표시더보기 버튼 눌렀을 때 추가 데이터 요청포켓몬 고유 ID(숫자)를 사용하여 이전/다음 페이지 구현포켓몬의 타입에 따른 데미지 상성 관계를 모달 창으로 제공 구현하기띠부띠부씰 느낌으로 css를 넣어봤다.폴더구조📁src ├── App.js ├── App.css ├── 📁components │ ├── Modal.js │ ├── Nav.js │ ├── PokemonList.js │ └── SearchInput.js ├── 📁hooks │ ├── useFetchPokemonData.js │ └── useFetchPokemonDetail.js ├── 📁pages │ ├── Detail.js │ └── Main.js메인에서 추가 데이터 요청하기PokemonList.js 과 useFetchPokemonData.js 훅useFetchPokemonData.js : 페이지 번호에 따라 Pokémon 데이터를 가져오는 역할export const useFetchPokemonData = (pageNumber) => { const [pokemonData, setPokemonData] = useState([]); const [isLoading, setIsLoading] = useState(false); useEffect(() => { const fetchPokemonData = async () => { setIsLoading(true); try { const promises = []; // 한 페이지당 20개의 포켓몬 데이터 가져오기 const start = (pageNumber - 1) * 20 + 1; // 예: 1 페이지면 1~20, 2페이지면 21~40 const end = pageNumber * 20; for (let i = start; i <= end; i++) { promises.push(axios.get(requests.fetchPokemonById(i))); } const responses = await Promise.all(promises); const newData = responses.map((response) => response.data); setPokemonData((prevData) => [...prevData, ...newData]); // 기존 데이터에 추가 } catch (error) { console.error('포켓몬 데이터를 가져오는 중 오류 발생:', error); } finally { setIsLoading(false); } }; fetchPokemonData(); }, [pageNumber]); return { pokemonData, isLoading }; };상태관리 :pokemonData: 가져온 Pokémon 데이터 배열을 저장isLoading: 데이터 로딩 중 여부를 보여준다. (true 또는 false) 데이터 가져오기 :useEffect 훅을 사용하여 pageNumber가 변경될 때마다 데이터를 가져온다.fetchPokemonData라는 비동기 함수를 정의하고, 여기서 Pokémon 데이터를 API를 통해 가져옴1. const { pokemonData, isLoading } = useFetchPokemonData(pageNumber); 훅에 pageNumber = 1 전달2. fetchPokemonData 함수 내에서const start = (pageNumber - 1) * 20 + 1; // 예: 1 페이지면 1~20, 2페이지면 21~40 const end = pageNumber * 20;start 계산 : pageNumber 가 1일때 :첫 번째 페이지의 첫 번째 Pokémon ID가 1임을 나타낸다.end 계산 : 첫 번째 페이지의 마지막 Pokémon ID가 20임을 나타낸다.3. 데이터 요청for (let i = start; i <= end; i++) { promises.push(axios.get(requests.fetchPokemonById(i))); }baseUrl(axios) + fetchPokemonById (requests) → https://pokeapi.co/api/v2//pokemon/${id}for 루프를 통해 Pokémon 데이터를 가져오기 위해 API에 요청하고 const promises = []; 배열에 데이터 추가함4. 데이터 수신 및 상태 업데이트const responses = await Promise.all(promises); const newData = responses.map((response) => response.data); setPokemonData((prevData) => [...prevData, ...newData]); responses 모든 Promise가 이행되면, 이행된 결과를 포함하는 배열을 반환, responses는 20개의 응답 객체를 포함newData responses 배열에서 각 응답의 실제 데이터 부분을 추출setPokemonData() 기존 데이터에 새 데이터 추가 PokemonList.js더보기 데이터 요청const handleLoadMore = () => { setPageNumber((prevPageNumber) => prevPageNumber + 1); };사용자가 '더보기' 버튼을 클릭하면 handleLoadMore 함수가 실행 이 함수에서는setPageNumber를 호출fetchPokemonData 함수 내에서 start와 end 값을 다시 계산start = (2 - 1) * 20 + 1 = 21end = 2 * 20 = 40{!isLoading ? ( <button className='my-9 text-lg bg-gray-200 text-gray-600 px-9 py-2 rounded' type='button' onClick={handleLoadMore} > 더보기 </button> ) : ( <div className='my-9 text-lg'>Loading...</div> )}처음에는 const [isLoading, setIsLoading] = useState(false); 표시 → setIsLoading(true); 상태 업데이트isLoading이 true일 경우 'Loading...' 메시지를 표시하고, false일 경우 '더보기' 버튼을 보여준다. 포켓몬 타입별 색상코드 적용..bg-normal { background-color: #949495; }api데이터로 타입을 받아서 bg-${type} 형식으로 class지정.css에서 타입별 색상 코드를 적용해 놓는다. 포켓몬 넘버 3자리 수로 변경 1 → 001{String(pokemon.id).padStart(3, '0')} 포켓몬 검색 기능검색기능export default function SearchInput() { const [searchTerm, setSearchTerm] = useState(''); const navigate = useNavigate(); const handleSearch = () => { if (searchTerm.trim()) { navigate(`/${searchTerm.toLowerCase()}`); } }; return ( <div className='max-w-3xl py-7 w-full flex justify-center items-center mx-auto'> <div className='flex w-full max-w-[600px] rounded-xl border-2 border-gray-200 overflow-hidden'> <input type='text' className='px-5 py-4 w-full focus:outline-none' value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} placeholder='포켓몬 이름을 입력하세요' /> <button className='whitespace-nowrap px-9 bg-gray-200' onClick={handleSearch} > 검색 </button> </div> </div> ); }포켓몬 이름을 입력하고 검색 버튼을 누르면 해당 이름의 페이지로 이동한다. 포켓몬 영문 이름은 소문자이기 때문에 toLowerCase() 를 사용해서 입력된 값이 소문자로 넘어가게 한다. trim()으로 있을지 모르는 공백 제거도 해준다. 디테일 페이지와 모달Detail.js 컴포넌트와 useFetchPokemonDetail.js 훅Detail.js 컴포넌트는 특정 포켓몬의 세부 정보를 보여주는 컴포넌트.포켓몬 이름을 URL에서 가져와 해당 포켓몬의 데이터를 요청하고, 포켓몬 설명 및 타입에 따른 데미지 관계 정보를 함께 표시한다.export default function Detail() { const { id: pokemonName } = useParams(); const { pokemon, description, damageRelations } = useFetchPokemonDetail(pokemonName); const [isModalOpen, setIsModalOpen] = useState(false); const navigate = useNavigate(); if (!pokemon) { return <div>Loading...</div>; } // 모달 열기/닫기 기능 const handleOpenModal = () => setIsModalOpen(true); const handleCloseModal = () => setIsModalOpen(false); // 이전 포켓몬 데이터로 이동 (ID로 요청) const handlePrev = () => { const prevId = pokemon.id - 1; if (prevId >= 1) { axios.get(requests.fetchPokemonById(prevId)).then((response) => { setIsModalOpen(false); navigate(`/${response.data.name.toLowerCase()}`); }); } }; //다음 포켓몬 const handleNext = () => {/*생략*/} return ( //생략 {/* 모달이 열렸을 때만 렌더링 */} {isModalOpen && ( <Modal damageRelations={damageRelations} onClose={handleCloseModal} /> )} ) };1. 포켓몬 이름 가져오기 useParams 사용const { id: pokemonName } = useParams();PokemonList.js 에서  onClick={() => navigate(`/${pokemon.name}`)} URL에 있는 포켓몬 이름을 가져와서 id 값으로 쓴다. 2. 훅 useFetchPokemonDetail.js 포켓몬 이름으로 데이터 요청const { pokemon, description, damageRelations } = useFetchPokemonDetail(pokemonName); 해당 포켓몬의 정보 (pokemon), 설명 (description), 데미지 관계 (damageRelations)를 받아온다. 3. 로딩 상태 처리포켓몬 데이터가 아직 로드되지 않았다면 Loading... 메시지를 표시한다. if (!pokemon) { return <div>Loading...</div>; } 4. 모달 열기/닫기: const [isModalOpen, setIsModalOpen] = useState(false); // 모달 열기/닫기 기능 const handleOpenModal = () => setIsModalOpen(true); const handleCloseModal = () => setIsModalOpen(false);isModalOpen 상태를 사용하여 모달의 열림/닫힘을 관리한다. 5. 이전/다음 포켓몬으로 이동: const handlePrev = () => { const prevId = pokemon.id - 1; if (prevId >= 1) { axios.get(requests.fetchPokemonById(prevId)).then((response) => { setIsModalOpen(false); navigate(`/${response.data.name.toLowerCase()}`); }); } };API를 통해 받아온 포켓몬의 고유 ID 값 사용.handlePrev 함수는 현재 포켓몬의 ID에서 1을 빼서 이전 포켓몬으로 이동한다.ID가 1 이상일 경우, 이전 포켓몬의 데이터를 요청하고, 응답받은 이름으로 URL을 변경. 다음 이동 함수도 같은 내용. useFetchPokemonDetail 훅 : 포켓몬의 이름을 받아 해당 포켓몬의 세부 정보, 설명, 데미지 관계 정보를 가져온다.export const useFetchPokemonDetail = (pokemonName) => { //생략 return { pokemon, description, damageRelations }; }; 1. 상태관리  const [pokemon, setPokemon] = useState(null); const [description, setDescription] = useState(''); const [damageRelations, setDamageRelations] = useState(null);pokemon: 포켓몬의 기본 데이터를 저장description: 포켓몬의 설명을 저장damageRelations: 포켓몬 타입에 따른 데미지 관계 데이터를 저장 2. 포켓몬 데이터 요청:useEffect(() => { const fetchPokemonDetail = async () => { try { // 포켓몬 기본 정보 요청 const response = await axios.get( requests.fetchPokemonByName(pokemonName) ); setPokemon(response.data); // 포켓몬 설명 const speciesResponse = await axios.get( requests.fetchPokemonSpecies(pokemonName) ); const flavorTextEntry = speciesResponse.data.flavor_text_entries.find( (entry) => entry.language.name === 'en' ); setDescription( flavorTextEntry ? flavorTextEntry.flavor_text : 'No description available.' ); // 포켓몬 타입에 따른 데미지 관계 가져오기 if (response.data.types.length > 0) { const typeName = response.data.types[0].type.name; // 첫 번째 타입 선택 const typeResponse = await axios.get( requests.fetchPokemonType(typeName) ); setDamageRelations(typeResponse.data.damage_relations); // 데미지 관계 설정 } } catch (error) { console.error('포켓몬 데이터를 가져오는 중 오류 발생:', error); } }; fetchPokemonDetail(); }, [pokemonName]);useEffect는 pokemonName이 변경될 때마다 실행된다.axios.get을 사용하여 포켓몬 이름으로 기본 데이터를 요청하고, setPokemon으로 상태를 업데이트.가져온 데이터에서 영어 설명(flavor_text_entries)을 찾아서 설명 상태에 저장 만약 설명이 없으면 기본 메시지를 설정한다.포켓몬의 타입 정보가 있으면 첫 번째 타입의 이름을 사용하여 해당 타입에 대한 데미지 관계 정보를 요청하고 이 데이터를 damageRelations 상태로 저장.pokemon, description, damageRelations 데이터를 반환하여 Detail.js에서 사용할 수 있게 한다.   

워밍업클럽

Taeho

인프런 워밍업 클럽 - CS Day 11

Algorithm영역을 2개로 나누어서 정렬을 수행하는 알고리즘정렬된 영역정렬되지 않은 영역정렬되지 않은 영역에서 데이터를 하나씩 꺼내서 정렬된 영역 내의 적절한 위치에 삽입하여 정렬하는 알고리즘시간 복잡도O(n^2) : 배열이 역순으로 정렬되어 있는 경우장점구현이 간단하고 이해하기 쉽다.추가적인 메모리 소비가 적다.단점시간 복잡도가 O(n^2)로 느리다.데이터의 상태에 따라 성능 편차가 크다.최선의 경우 : O(n)최악의 경우 : O(n^2)OS가상 메모리프로세스는 OS 영역이 어디있는지, 물리 메모리의 크기가 얼마나 큰지 몰라도 된다.→ 프로그래머는 물리 메모리의 크기와 프로세스가 메모리에 어느위치에 신경쓰지 않고 0x0번지에서 시작한다고 가정하고 프로그래밍을 한다.프로세스는 메모리 관리자를 통해서 메모리에 접근한다.직접 메모리에 붙지 않는다.가상 메모리의 크기이론적 무한대실제 : 물리 메모리 크기 + CPU 비트 수ex) 32bit → 표현 가능한 주소값 : 2^32 = 4GB가상 메모리 시스템은 물리 메모리가 처리할 수 없는 용량을 프로세스들이 요구할 때 물리 메모리 내용의 일부를 하드 디스크에 있는 스왑 영역으로 옮기고 처리가 필요할 때 물리 메모리로 가져와서 처리한다.→ 물리 메모리가 처리할 수 없는 용량의 작업들을 처리할 수 있게 한다.메모리 관리자는 가상주소와 물리 주소를 1 : 1 매핑 테이블로 관리한다.가상 메모리의 프로세스 할당 방법가상 메모리 시스템에서는 OS 영역을 제외한 나머지 영역을 일정한 크기로 나누어서 프로세스에게 할당한다.가변 분할 방식(Segmentation)프로그램은 함수나 모듈 등으로 세그먼트를 구성한다.각 세그먼트들이 인접해 위치할 필요는 없다.But. 프로세스 입장에서는 코드 영역, 데이터 영역, 힙 영역, 스택 영역을 서로 인접한 것 처럼 바라본다.MMU(메모리 관리자)가 논리주소로 물리주소로 변환해주는 방법세그멘테이션 테이블이라는 테이블이 존재Base AddressBound Address⇒ 두 주소를 사용하여 물리 메모리 주소를 계산한다.AddressBase Address프로세스나 세그먼트의 물리 메모리 상 시작 주소를 나타낸다.MMU의 base register에 저장되어 주소 변환에 사용Bound Address (또는 Limit)프로세스나 세그먼트의 크기를 나타낸다.MMU의 bound/limit register에 저장되어 메모리 보호에 사용된다.논리주소 → 물리주소CPU에서 논리 주소 전달MMU는 해당 논리 주소가 몇 번 세그먼트 인지 확인MMU내의 Segment Table Base Register 내에 있는 세그멘테이션 테이블을 찾는다.세그먼트 번호를 인덱스로 Base Address와 Bound Address를 찾는다.MMU는 CPU에서 받은 논리주소와 Bound Address의 크기를 비교한다.논리주소가 Bound Address보다 작은 경우논리주소와 Base Address를 더해 물리 주소를 더한다.논리주소가 Bound Address보다 큰 경우메모리를 침범했다고 간주하고 에러를 발생시킨다.STBR(Segment Table Base Register)OS는 컨텍스트 스위칭을 할 때마다 메모리 관리자내의 Segment Table Base Register를 해당 프로세스의 것으로 값을 바꿔줘야 한다.→ Context Switching이 고비용 작업인 이유장점메모리를 가변적으로 분할할 수 있다.코드 영역, 데이터 영역, 스택 영역, 힙 영역을 모듈로 처리할 수 있어 공유와 각 영역에 대한 메모리 접근보호가 편리하다.단점외부 단편화 발생고정 분할 방식(Paging)세그멘테이션 기법의 외부 단편화를 해결하기 위해 고안된 배치 정책페이징은 메모리를 할당할 때 정해진 크기의 페이지로 나눈다.→ 외부 단편화가 발생되지 않는다.Page : 일정한 크기로 균일하게 나뉘어진 논리주소공간Frame : 일정한 크기로 균일하게 나뉘어진 물리주소공간가장 신경써야 하는 점페이지 테이블의 크기각 프로세스마다 테이블을 갖는다.→ 프로세스가 많아질 수록 페이지 테이블도 많아진다.→ 프로세스가 실제로 사용할 수 있는 메모리 영역이 줄어든다.페이지 테이블 크기가 너무 큰 경우 : 사용자 영역이 부족하다.Page Table가상 주소를 물리 주소로 변환하는데 사용하는 테이블각 프로세스의 가상 페이지 번호(VPN)을 물리 프레임 번호(PFN)로 매핑한다.각 프로세스마다 독립적인 Page Table을 갖는다.페이지 테이블에 Invalid로 되어 있으면 스왑영역에 저장되어 있는 상태Page Table Entry(PTE)페이지 기본 주소 (Page base address)유효 비트 (Valid bit): 페이지가 메모리에 있는지 여부보호 비트 (Protection bit): 읽기/쓰기/실행 권한참조 비트 (Reference bit): 페이지 접근 여부수정 비트 (Dirty bit): 페이지 내용 변경 여부Page Table Base Register(PTER)각 프로세스의 PCB에 위치하며, 해당 프로세스의 Page Table의 시작 주소를 가리킨다.OS는 컨텍스트 스위칭을 할 때마다 메모리 관리자내의 Segment Table Base Register를 해당 프로세스의 것으로 값을 바꿔줘야 한다.논리주소 → 물리주소CPU에서 논리주소 전달MMU는 논리주소가 몇번 페이지인지 오프셋은 어떻게 되는지 확인MMU에서 Page Table Base Register를 이용하여 물리 메모리에 있는 페이지 테이블을 찾는다.페이지 번호를 인덱스로 프레임 번호를 알아낸다.오프셋을 사용하여 물리 주소로 변환한다.장점메모리를 효율적으로 관리할 수 있다.단점내부 단편화 발생세그멘테이션의 외부 단편화와 비교하면 많은 공간을 비교하지 않는다.Segmentation vs Paging차이점페이지의 크기세그멘테이션은 프로세스마다 크기가 달라 Bound Address를 갖는다.페이징은 모든 페이지의 크기가 동일해서 크기를 표현하는 Bound Address가 필요없다.세그멘테이션은 논리적인 영역별로 세그먼트를 나눈다.→ 세그먼트 마다 크기를 다르게 나눌 수 있다.→ 코드 영역, 데이터 영역, 스택 영역, 힙 영역을 나눌 수 있다.페이징은 페이지의 크기가 고정되어 있어 논리적인 영역을 나눌 수 없다.→ 특정 영역만 떼어내서 공유하거나 권한을 부여하기 어렵다.Paged Segmentation세그멘테이션 - 페이징 혼용 기법각각의 장점을 혼합한 기법세그멘테이션 테이블과 페이지 테이블을 결합하여 사용한다.메모리 접근 권한메모리의 특정 번지에 부여된 권한상태값 : Read, Write, Execute각 프로세스는 코드 영역, 데이터 영역, 힙 영역, 스택 영역이 존재한다.→ 각 영역마다 접근 권한이 있다.코드 영역 : 프로그램 자체 ⇒ 읽기/실행 권한데이터 영역 : 일반변수, 전역변수, 상수로 선언한 변수 저장 ⇒ 읽기/(쓰기-Optional) 권한힙 영역 : 읽기/쓰기 권한스택 영역 : 읽기/쓰기 권한메모리 접근 권한 검사는 가상주소에서 물리주소로 변환될 때마다 일어난다.권한을 위반하는 경우 에러를 발생시킨다.주소 변환 과정세그먼트 번호로 세그먼트 테이블에 접근하여 세그먼트 길이와 해당 세그먼트의 페이지 테이블 시작 주소를 얻는다.해당 세그먼트가 메모리 접근 권한을 위반하는지 검사한다.접근 권한 위반 시 프로세스 종료세그먼트의 페이지 테이블 시작 주소에 세그먼트 내 페이지 번호를 더해 페이지 테이블 항목에 접근한다.페이지 테이블에서 프레임 번호를 얻는다.프레임 번호에 페이지 내 오프셋을 더해 최종 물리 주소를 얻는다.단점물리 메모리에 접근하기 위해 메모리에 두 번 접근해야 한다.세그멘테이션 테이블 참조페이지 테이블 참조→ 페이징과 페이지드 세그멘테이션 기법을 적절히 섞어서 사용한다.DAT(Dynamic Address Translation, 동적 주소 변환)메모리 관리자가 물리 메모리와 스왑영역을 합쳐서 프로세스가 사용하는 가상주소를 물리주소로 변환하는 과정DAT를 거치면 프로세스는 마음대로 사용자 데이터를 물리 메모리에 배치할 수 있다.

알고리즘 · 자료구조워밍업클럽CS전공지식Day11

thagyun

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

[따라하며 배우는 자바스크립터, 리액트 A-Z - John Ahn] 배운 내용 정리섹션 5: 자바스크립트 중급 this:메소드 this는 해당 객체 참조함수 this는 Window object 참조생성자 함수 this는 빈 객체 참조화살표 함수 this는 상위 스코프 this 참조call(): 인자 값을 줘서 this가 가리키는 window 객체를 인자값으로 가리키게하고 바로 호출 func.call(object, arg1, arg2);apply(): arguments를 배열로 전달 func.apply(object, [arg1, arg2]);bind(): this에 객체만 바인딩하기 때문에 사용하려면 함수 호출 func.bind(object)(arg);conditional operator: 조건식 ? 참이라면 실행할 명령문 : 거짓이라면 실행할 명령문 JS는 동기 언어로 비동기 부분은 브라우저의 도움으로 처리한다. call stack에서 비동기 함수는 Web APIs으로 보내져서 비동기 처리가 완료되면 콜백 큐로 함수가 들어오게 되고 callback queue에서는 web api의 콜백 함수들이 대기하게 된다.이벤트 루프는 call stack과 callback queue를 계속 주시하고 있다가 call stack이 비게 되면 먼저 들어온 순서대로 콜백 큐에 있는 함수들을 call stack에 넣어준다. Closure란 다른 함수 내부에 정의한 함수가 있는 경우 해당 내부 함수는 외부 함수의 변수 및 범위에 엑세스할 수 있다. array methods:map: 배열 내의 모든 요소 각각에 대하여 주어진 함수를 호출한 결과를 모아 새로운 배열을 반환// 화살표 함수는 thisArg가 들어가지 않음 const map = arr.map(callback(currentValue[,index[,array]]),[, thisArg])filter: 주어진 함수의 테스트를 통과하는 모든 요소를 모아 새로운 배열 반환const filter = arr.filter(callback(element[,index[,array]]),[, thisArg])reducer: 배열의 각 요소에 대해 주어진 reducer 함수를 실행하고, 하나의 결과값 반환const reducer = arr.reduce(callback[, initialValue]) // 4개의 인자 // 1. 누산기 accumulator // 2. 현재 값 currentValue // 3. 현재 인덱스 currentIndex // 4. 원본 배열 array undefined: 자바스크립트 엔진이 변수를 초기화할 때 사용null: 개발자가 의도적으로 변수에 값이 없다는 것을 명시 ===: 원시 자료형 비교JSON.stringify: 객체가 깊지 않은 경우 비교lodash library: 객체가 깊은 경우 비교깊은 복사의 경우 lodash, ramda 등 라이브러리 또는 structuredClone 사용 함수 선언문: function 키워드 다음 함수 이름을 작성해 함수 이름 선언, 호이스팅 영향함수 표현식: 변수에 함수 할당, 함수 이름 익명 IIFE: 즉시 실행 함수 표현 ( function(){} )()사용 목적: 변수를 전역으로 선언하는 것을 피하고<( function(){} ) > IIFE 내부 안으로 다른 변수들이 접근하는 것을 막음<()> Intersection-observer: 뷰포트와 설정한 요소의 교차점을 관찰하며 요소가 뷰포트에 포함되는지 포함되지 않는지 구별하는 기능 제공observe() 메소드로 타겟 요소 선정, isIntersecting가 true라면 타겟 요소와 루트 요소가 교차한다는 뜻 순수 함수는 함수형 프로그래밍 패러다임의 한 부분으로 2가지 규칙을 갖는다. 같은 입력 값이 주어졌을 때, 언제나 같은 결과를 리턴한다. 사이드 이펙트를 만들지 않는다. 이 규칙을 따름으로써 클린 코드, 쉬운 테스트, 쉬운 디버깅, 독립적인 코드를 작성할 수 있다. 커링은 함수를 호출하는 것이 아닌 병합하는 것으로 커링을 통해 여러 인자를 단계별로 받아 원하는 시점에 최종 결과를 얻을 수 있어 함수 재사용성과 유연성에 향상된다. func()()() strict mode: 기존에 무시되던 에러들을 발생하게 하고, 최적화 작업을 어렵게 만드는 실수를 바로 잡아준다.적용법:파일에 use strict 지시자 입력함수에서만 사용하려면, function func() {"use strict"; return [];}class 문법을 사용하면 자동으로 변경html <script src="" type="module"></script> type="module" 이 부분은 자바스크립트의 꽃이 아닐까 생각이 드는 섹션이었다. 프론트엔드 면접 질문 3대장인 this, 실행 컨텍스트, 클로저 부분을 배울 수 있었다. 사실 대학다닐때 웹프로그래밍을 수강했음에도 이부분에 대해서는 따로 학습하지 못했다. 이번에 학습하면서 와 이게 이렇게? 라는 부분이 많았다. 아직 프론트엔드 개발자로서 지식이 참 부족하다는 생각이 들었다. 섹션 6: OOPOOP: 여러개의 독립된 단위 "객체" 들의 모임특징:자료 추상화상속다형성캡슐화오버로딩(Overloading): 같은 이름의 메서드 여러개를 가지면서 매개변수의 유형과 개수가 다르도록 함오버라이딩(Overriding): 상위 클래스가 가지고 있는 메서드를 하위 클래스가 재정의해서 사용 Polymorphism: 같은 메소드라도 각 인스턴스에 따라 다양한 형태를 가질 수 있는 것 Prototype: 자바스크립트 객체가 다른 객체로부터 메서드와 속성을 상속받는 매커니즘생성자함수.prototype.함수 = function(){};Object.create(prototype)ES6 Classes는 Java와 사용법이 비슷했다.Sub Class(Inheritance): 부모 클래스에 있던 기능을 토대로 자식 클래스를 만들 수 있는 것, extends 키워드 사용super(): 자식 클래스 내에서 부모 클래스의 생성자/메소드를 호출할 때 사용 분명 Java 배우면서 OOP에 대해 알고 있다 생각했지만,  오버로딩, 오버라이딩이 헷갈리고 prototype이 나오자 당황해버렸다. 다시 재학습해야 할 것 같다. 섹션 7: 비동기자바스크립트는 싱글스레드이기 때문에 하나의 일이 오래 걸리면 다른 작업들은 그 하나의 일이 끝날때 까지 기다려야 한다.이러한 문제점을 해결하기 위해 비동기로 어떠한 일을 수행하게 된다.비동기 요청이 다른 비동기 요청의 결과에 의존한다면 Callback, Promise, Async/Await를 통해 문제를 해결할 수 있다.Callback(): 콜백 함수는 특정 함수에 매개변수로 전달된 함수를 의미하고 그 콜백 함수는 함수를 함수를 전달받은 함수 안에서 호출 된다.코드 가독성이 떨어지고 에러 처리를 한다면 모든 콜백에서 각각 에러 핸들링을 해줘야 한다.Promise: new 키워드와 생성자를 사용해 Promise 객체를 만들고 생성자는 매개 변수로 resolve, reject 실행 함수를 받는다.const prom = new Promise((resolve, reject) => {}); prom .then((success) => { }) .catch((err) => { }) .finally(() => { }); Promise의 상태:대기(pending): 비동기 처리 로직이 아직 완료되지 않은 상태이행(fulfilled): 비동기 처리가 완료되어 프로미스가 결과 값을 반환해준 상태거부(rejected): 비동기 처리가 실패하거나 오류가 발생한 상태// 모두 이행되면 결과 값 실행 Promise.all(순회 가능한 객체).then(() => {}); // iterable 안에서 가장 먼저 완료된 프로미스 결과값을 이행하거나 거부 Promise.race(순회 가능한 객체).then(() => {});fetch()는 promise를 지원하기 때문에 new promise 생성자를 사용하지 않아도 된다.fetch(비동기 요청).then(() => {});Async/Await:비동기 코드를 마치 동기 코드 처럼 보이고 코드 가독성이 좋다.await는 async 안에서만 사용해야 하고, try... catc구문을 사용할 수 있다.async function func() { try { const res1 = await fetch("http://abcdefg.com"); const json1 = await res1.json(); } catch (err) { console.log(err); } finally { console.log("done."); } } func(); 섹션 8: Iterator, GeneratorSymbol은 유니크한 식별자를 만들기 위해 사용한다.const sym = Symbol('val'); // 심볼 생성 console.log(sym.description); // val // Symbol.for(key): 동일한 Symbol 공유 const sym1 = Symbol.for("hi"); const sym2 = Symbol.for("hi"); console.log(sym1 === sym2); // true // Symbol.keyFor(symbol): 특정 심볼의 키 반환 const sym3 = Symbol.for("hello"); console.log(Symbol.keyFor(sym3)); // "hello"  Iterator는 Iterable 객체와 함께 사용되고 next() 메서드를 이용해 컬렉션의 요소를 순차적으로 접근할 수 있게 하는 객체다.- next() 메서드를 호출할 때마다 객체의 다음 값 반환- { value: 값, done: boolean } 형태의 객체 반환 vlaue 현재 값, done 반복이 끝났는지를 나타낸다.const arr = [1, 2]; const iterator = array[Symbol.iterator](); console.log(iterator.next()); // {value: 1, done: false} console.log(iterator.next()); // {value: 2, done: false} console.log(iterator.next()); // {value: undefined, done: true}  Generator는 Iterator를 쉽게 구현할 수 있도록 도와주는 함수로 function* 키워드를 사용하여 정의한다.함수 안에서 yield 키워드로 값을 반환한다.Generator는 호출되면 바로 실행되지 않고, 반복 제어가 가능하다.yield를 사용하여 값을 반환하고 이 값을 next() 메서드로 받아온다.function* generator() { yield 1; yield 2; yield 3; } const gen = generator(); console.log(gen.next()); // { value: 1, done: false } console.log(gen.next()); // { value: 2, done: false } console.log(gen.next()); // { value: 3, done: false } console.log(gen.next()); // { value: undefined, done: true } 섹션 9: Design Pattern디자인 패턴은 문제에 대한 일반적이고 재사용 가능한 솔루션을 말한다. - Singleton 패턴: 클래스의 인스턴스화를 하나의 객체로 제한하는 디자인 패턴- factory 패턴: 동일한 코드를 계속해서 반복할 필요 없이 동일한 속성을 공유하는 여러 객체를 만들어야할 때 유리- mediator 패턴: 객체 그룹에 대한 중앙 권한을 제공- observer 패턴: event-driven 시스템 이용하는 것을 말하고 게시자-구독자 모델이라고도 한다.- module 패턴: export로 모듈을 내보내고 import로 모듈을 가져온다.script 타입에 module로 명시하여 사용한다. 모듈의 특징으로는 항상 엄격 모드로 실행하고 지연 실행과 인라인 모듈 스크립트를 비동기로 처리할 수 있고, 외부 오리진에서 스크립트를 불러오려면 CORS 헤더가 있어야 하고 중복된 스크립트는 무시된다. 섹션 10: 프로젝트 만들기 2주차 회고:1주차 KPT 회고할 때 분명 리액트가 시작되기 전에 밀린 강의를 마무리 하기로 했지만.. 아직도 자바스크립트 강의가 끝나지 않았습니다. 분명 배운 내용도 있어서 금방 끝낼 수 있다고 생각했는데 근거 없는 자신감이었습니다. 새로 보는 내용이네요.. 중간점검에 참여할 때 강사님께서 말씀하신 제출을 위한 발자국 굉장히 찔렸습니다ㅎㅎ.. 부끄럽네요.. 화요일까지 남은 부분 정리하여 업데이트 하겠습니다. 다음주가 마지막이 되는데 끝까지 포기하지 않고 과제까지 끝내보도록 하겠습니다! (중꺾마)

ykm8864

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

워밍업 클럽 백엔드 스터디 2기 2주차 발자국 입니다.우선 객체의 책임과 추상화레벨에 근거한 코드다듬기에 대하여 배웠습니다.주석의 양면성주석이 많다는 것은, 그만큼 비지니스 요구사항을 코드에 잘 못 녹였다는 이야기좋은 주석이란 → 우리가 가진 모든 코딩 표현 방법을 총동원해 코드에 의도를 녹여내고, 그럼에도 불구하고 전달해야 할 정보가 남았을 때 사용하는 주석 변수와 메서드의 나열 순서변수는 사용하는 순서대로 나열한다. (인지적 경제성을 위해)메서드의 순서는 공개메서드를 상단에 배치하고 private 메서드를 후순위로 둔다. 이때 각 메서드 그룹(public, private) 안에서는 상태변경 > 판별 >= 조회 메서드 순으로 배치시키는 것이 좋다.메서드를 만들거나 추출할 때마다 2가지 생각을 할 줄 알아야한다. 1. 어? 이 메서드를 어디쯤 둬야하지 2. 어?이게 private 인가 public인가? 외부에서 내부로 쓰임새가 변경되었는지까지 한번 더 확인 패키지 나누기패키지는 문맥으로써의 정보를 제공할 수 있다.하지만 멋대로 바꾸면 안된다. 협업 안에서의 안정성과 팀원을 배려함을 확보하면서 작업해야한다. 알고리즘 최적화스택오버플로우? 재귀? dfs? 스택 ? 데큐? 더 좋은 성능이 무엇일까특정 알고리즘의 성격을 띄는 비지니스 로직이 있을 수 있다. 이는 기본적인 자료구조와 알고리즘에 대한 이해를 기반으로 최적화 해나갈 수 있다. 강의에서는 dfs라는 배경지식을 기반으로 재귀를 스택자료구조로 표현했고 이후 데큐를 통해 성능을 최적화했다. ide의 도움받기코드 포맷 정렬, Sonarlint, editorconfig발전하는 it의 특성상 여러가지 툴도 동시에 발전해나가고 있다. 툴의 도움을 꺼려하지말자. 더 많은 것을 배울 수 있다. 능동적 읽기뒤가 있어 우리에겐 " git reset --hard"코드를 읽고 이해하려고 할 때, 공백으로 단락을 구분하고 의미와 책임으로 추상화를 해보고, 주석을 직접 달아가면서 해석을 해보자. 도메인의 지식을 늘리면 코드의 추상화가 더 쉬워진다. 도전을 두려워하지마 우리게엔 git reset --hard가 있다.오버엔지니어링정말 필요한 코드 구조인가?도메인의 지식에 벗어난 과한 추상화, 과한 인터페이스는 오히려 오버엔지니어링일 수 있다. 은탄환은 없다.정답은 없다.클린코드는 모든 방법의 핵심이 아니다. 클린코드는 미래의 일어날 수 있는 변화에 대하여 잘 대응할 능력을 제공해준다. 이러한 사고법을 기반으로 언제 적재적소에 알맞게 클린코드가 필요한지 파악할 수 있다. 모든 기술의 방법록은 정적 기술의 범위 내에서 적용되어야 좋은 기술이다. 1주차 회고Keep (만족했고, 앞으로도 지속하고 싶은 부분)추상화와 조금 더 친해질 수 있었다. 클린코드를 배우다보면 초창기에는 모든 코드가 불편하게 보이게 되는 경향이 있다. 하지만 이번 차수를 마치면서 은탄환은 없다. 적재적소에 맞는 기술과 코드를 쓰는 것이 중요하다라고 느꼈고 이 점을 회사에 적용해보니 더 많은 사람들이 나의 생각을 이해하고 따라와주는 것에 고마움과 만족감을 느꼈다. 앞으로 더 넓은 객체의 세계를 배워 더 좋은 설계를 해보고 싶다. .Problem (아쉬웠던 점)알고리즘과 자료구조에 대한 깊은 이해가 부족하다는 생각이 들었다. 알고리즘을 겉햝기 식으로 풀어는 왔지만 막상 코딩테스트 문제를 통해 접근하는 알고리즘과 실제 비지니스 도메인로직에 알고리즘을 녹이는 것은 차원이 다르다는 생각이 들었다. 우선 원초적인 기본부터 지켜나가면서 공부를 더 해야함을 느꼈다.  Try (다음에 시도해볼 점)주석을 쓴다는 것은, 내 코드를 다른 사람들이 읽기 쉽게 해준다고 생각했었다. 예를 들어 회사에서 잘 쓰지 않는 신규 api나 메서드를 가지고 코딩을 하면 그 코드에 대한 해석이 어려운 부분이 있을 거라 생각해서 그랬던 거 같다. 하지만 이번 강의를 통해 더 친절한 코딩을 하면 주석따위는 필요없다고 생각이 들었다. 앞으로 코딩을 할 때 과연 내가 후손에게 친절한 코드를 작성하고 있는가 에 데한 고민을 수시로 해봐야겠다.

웹 개발몰입하는개발자워밍업클럽발자국성장

[인프런워밍업스터디_BE_2기] 2주차 발자국

Readable Code: 읽기 좋은 코드를 작성하는 사고법 수강 후 작성한 글입니다. 2주차 정리2주차에는 이전까지 리팩토링한 코드를 좀 더 다듬고, 주어진 코드를 직접 리팩토링해보는 시간을 가졌다. 업무 시간에도 계속 어렵게 느껴졌던 패키지 정리나 클래스 정리 부분이 많은 도움이 되었고, 시간이 부족해 리팩토링을 제대로 해보지는 못하였지만 시야를 많이 넓힐 수 있었다.마지막 섹션8에서 은탄환은 없다는 제목으로 조언을 해주신 것이 가장 기억에 남았다. 좋은 코드를 작성하기 위해 책을 읽거나 강의를 듣고, 많은 자료를 찾아보면서 어느샌가 '좋은 코드' 라는 것에 목이 매여있던 것 같다. 특히나 업무가 바쁜 지금 시간내에 코드를 작성하는 것이 더 중요함에도 작성한 코드가 마음에 들지 않아 다시 리팩토링을 하기도 하는 등 어떤 것을 우선해야하는지 잠시 잊었던 것 같다. 2주차 과제 정리2주차 Day7 과제[섹션 7. 리팩토링 연습]의 "연습 프로젝트 소개" 강의를 보고,'스터디 카페 이용권 서택 시스템' 프로젝트에서 지금까지 배운 내용을 기반으로 리팩토링을 진행해 봅시다.https://github.com/HyeongSeop-Kim/readable-code/tree/main/src/main/java/cleancode/studycafe 2주차를 마치며아쉬웠던 점0기부터 시간이 부족해도 매번 진도에 맞춰 따라갔는데 이번주에는 업무에 치여서 결국 진도를 맞추지 못했다... 그래서 발자국도 하루 늦게 쓰고 있다ㅠ 칭찬할 점그래도 끝까지 하는 것이 중요하다고 생각해 포기하지 않고 진도를 따라가고 있는 것은 칭찬할 수 있지 않을까..! 보완할 점회사 일이 너무 바빠서 어떻게 보완해야할 지 잘 모르겠지만.. 끝까지 최선을 다해봐야겠다!

백엔드

김현규

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

Day 6 Iterator, Generator, Design Pattern요즘 실무를 하면서 디자인 패턴에 관심이 점점 더 생기고 있다. 현재 내가 진행하고 있는 프로젝트는 아토믹 패턴을 사용해 함수형 프로그래밍을 적용하고 있지만, 최근 대부분의 트렌드는 객체지향 쪽으로 흐르는 것 같다.강의에서는 싱글턴(Singleton), 팩토리(Factory), 미디에이터(Mediator), 옵저버(Observer), 모듈(Module) 패턴을 배우고 있는데, 이 패턴들도 전부 객체지향적 패턴이다.현재 진행 중인 프로젝트는 어쩔 수 없이(능력의 부족으로..) 함수형 프로그래밍으로 가야 하지만, 앞으로 진행할 프로젝트에서는 어떤 방식을 사용하는 것이 더 효율적일지 고민하고 있다. 객체지향 프로그래밍이 더 나을지, 함수형 프로그래밍을 유지할지 앞으로 많이 찾아봐야겠다.미션: 비밀번호 생성 앱사실 강의보다는 과제 위주로 먼저 진행을 해서 자바스크립트로 해야 되는 과제도 모두 리액트로 진행했다.Day 7 프로젝트 만들기 미션: 타이핑 테스트 앱생각보다 많은 시간이 걸렸다.. CPM, WPM 계산 식도 헷갈렸고, 특히 html/css 에 매우 취약해서 그 부분에 시간을 많이 소모했다.총 10개의 문장에서 랜덤하게 2문장을 타이핑 테스트 하게 구현했다.재시작 버튼을 누르면 한 문장만 테스트하고 결과를 출력하길래 계속 무슨 문젠지 모르고 있었는데 그냥 브라우저에 캐시가 남아 있어서 났던 오류였다..Day 9 리액트 기본 및 Todo 앱 만들기현재 프로젝트가 기획 및 디자인 단계라, 업무적으로는 여유가 있어서 회사 업무 시간에도 공부를 틈틈이 하는데, 그래도 이어폰 끼고 강의 듣기에는 눈치가 보여서 리액트는 일단 공식 문서를 쭉 읽어보고 현재 프로젝트의 코드를 리딩하는 식으로 공부를 하고 있다. 미션: 예산 계산기 앱 Day 10 리액트로 Netflix 만들기항상 학습하는 성향 자체가 일단 무작정 시도하는 식으로 하는데, 이 부분부터는 그런 식으로 안될 것 같아서, 현재 강의 밀린 부분을 먼저 천천히 보려고 한다. 남은 기간 동안 다른 앱들을 얼마나 완성할 수 있을지 모르겠다. 취직하기 전에는 안 하던 공부를 하려니까 적응이 정말 힘들고, 마인드셋을 잡는 것도 어려운 것 같다. 회사를 다니면서도 워밍업 클럽에 참여하시는 분들을 보니 정말 존경스럽다.현재 강의가 조금 밀려 있는데, 남은 기간 동안에는 앱 구현보다는 강의를 들으며 기초에 더 집중하려고 한다.

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

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 2주는 너무 정신없이 호다닥 가버렸다 내 1주차 발자국때 미리 열심히 하자는 나의 다짐은... 흙그래도.. 또 나는 다음주는 열심히 하자는 꿈을 꾸지...적용하기에는 아직 버거웠다.. 숙제로 cafe machine 리팩토링해야했는데.. 일급클래스 나도 적용하고 싶었는데 할줄몰라서 슬펐다.. 그래도 같이 온보딩 스터디하시는 분이 리펙토링 숙제 코드 리뷰해주시면서 알려주셔서 이해가 가서 감사했당.. 강의는 허겁지겁 코드 따라쳐도 몬가 막상 내가 적용하기에는 너무 어렵더쿤...   4LS-회고법을 사용해보장!Liked (좋았던 점) 강의하면서 배우고 배웠던 것들을 적용할수있도록 리팩토링 숙제를 주셔서 유익했다.. 혼자서 해보는 실습 중요하지! 내가 잘 적용을 못했던것 뿐..Lacked (아쉬웠던 점)시간을 더 많이 투자해서 더 노력했어야하는데 아쉽다 ㅠ.ㅠ 다음주는 조금이라도 더 노력하는 내가 되었으면.. 햐그리고 까먹으니 계속 메모하면서 강의 듣기 ㅠㅠㅠㅠㅠLearned (배운 점)일급컬랙션 이름이 멋져보여서 다음에 내 프로젝트에 꼭 적용해보고 싶다 1- 상속과 조합상속 보다는 조합을 사용하자. 상속은 틀을 만들어 버려서 부모를 수정하면 자식들도 다 영향을 받는다. 부모와 자식과의 결합도가 높다. 2- Value Object, Entityvalue object 가 가져야하는 3가지- 불변성, 동등성,유효성 검증 entity는 식별자가 있고 valueObject는 식별자가 없지만 hashcode&equal을 사용해서 모든 필드들이 식별자 역할을 한다. Entitypublic class Member { private Long id; // 고유 식별자 private String name; }Value objectpublic class Address { private String city; private String street; private String zipcode; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Address address = (Address) o; return city.equals(address.city) && street.equals(address.street) && zipcode.equals(address.zipcode); } @Override public int hashCode() { return Objects.hash(city, street, zipcode); } } 3- 일급 컬랙션일급컬랙션은 list, set, map 이런 타입들의 추상화 도구로 사용한다 new로 2차 가공해서 해서 일급 컬랙션을 변경해도 이전의 컬래션에겐 영향이 없다.이번 리팩토링에선 실패했지만 다음에 리팩토링할때 꼭 적용해봐야지...Student 클래스public class Student { private final String name; private final int age; public Student(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } @Override public String toString() { return "Student{name='" + name + "', age=" + age + "}"; } }  StudentGroup 일급컬랙션 클래스import java.util.ArrayList; import java.util.Collections; import java.util.List; public class StudentGroup { private final List<Student> students; public StudentGroup(List<Student> students) { this.students = new ArrayList<>(students); // 불변성 유지 } public void addStudent(Student student) { this.students.add(student); // 학생 추가 } public List<Student> getStudents() { return Collections.unmodifiableList(students); // 불변 리스트 반환 } public double getAverageAge() { return students.stream() .mapToInt(Student::getAge) .average() .orElse(0.0); // 학생 나이의 평균 계산 } @Override public String toString() { return "StudentGroup{students=" + students + "}"; } } 4- Enum열거형 타입 상수의 직합- 상수에 대한 로직도 담을수있다. 여기에 설명도 넣을수 있다public enum Month { JANUARY("1월", 1), FEBRUARY("2월", 2), MARCH("3월", 3), APRIL("4월", 4), MAY("5월", 5), JUNE("6월", 6), JULY("7월", 7), AUGUST("8월", 8), SEPTEMBER("9월", 9), OCTOBER("10월", 10), NOVEMBER("11월", 11), DECEMBER("12월", 12); private final String description; // 월에 대한 설명 private final int order; // 월의 순서 Month(String description, int order) { this.description = description; this.order = order; } public String getDescription() { return description; } public int getOrder() { return order; // 월의 순서 반환 } // 주어진 숫자에 해당하는 월을 반환하는 메서드 public static Month valueOf(int order) { for (Month month : Month.values()) { if (month.getOrder() == order) { return month; } } throw new IllegalArgumentException("유효하지 않은 월 순서: " + order); } }이렇게 사용할수있다.. 신기한 코딩의 세계... io에서 1이 들어오면 if (input==1)이면 January 리턴하는게 아니라 이런식으로 하는게 아니라 return Month.valueOf(input)하면 January를 리턴한다!  5- 세션 6좋은 주석은 주석없이 설명이 가능한게 좋지만 어떤 이유로 방식을 결정하거나 그런 코드로는 알수없었던 필요한 디테일들은 주석으로 잘설명해두고 필요없는 주석은.. 지우기 (뜨끔)메서드 순서 -상태변경, 판별, 조회 순으로 메서드를 배치하고 public 은 위에 private은 밑에 배치한다.Longed for (앞으로 바라는 점)다음주 테스트코드 스터디가 아주 기대됩니당!! 

인프런온보딩리더블코드

유진

Node.js 개발자의 코-프링 적응기(2) | 인프런 워밍업 클럽 2기 - 백엔드

인프런 워밍업 클럽 2기입문자를 위한 Spring Boot with Kotlin(https://inf.run/bXQQQ) 강의를 듣고 작성하였습니다. h2자바로 구현된 인 메모리 DB 입니다. 애플리케이션이 종료되면 모든 메모리가 삭제됩니다.(휘발성) 프로젝트 내부에 임베드 해서 브라우저를 통해 매니징을 할 수 있다는 점이 특이했습니다. 겪은 오류 - user 테이블이 생기지 않음예전에 회사에서 user 테이블에 대해 데이터베이스를 명시하지 않아 오류가 발생한 적이 있었는데, 비슷한 상황인거 같아, 테이블 이름을 명시하였습니다.https://velog.io/@readnthink/DataJpaTest사용시-user-table-예약어-에러엔티티 정의 어노테이션@Table 와 @Entity테이블은 물리적인 정보, 엔티티는 논리적인 정보를 정의하는 걸까? 싶었는데, 아니었던 것 같습니다. 아예 목적이 좀 다른 느낌..?@Entity is useful with model classes to denote that this is the entity or table@Table is used to provide any specific name to your table if you want to provide any different namehttps://stackoverflow.com/questions/18732646/name-attribute-in-entity-and-tablehttps://www.inflearn.com/community/questions/75556 엔티티랑 테이블 어노테이션 내부에 들어가도 자세하게 테이블 옵션을 정의한다기보다는 느낌보다는 정말 bean임을 명시하기 위한 식별자라고 생각이 들었습니다. // Sequelize entity 정의 예시 @Table({ tableName: 'charge', timestamps: true, paranoid: true, createdAt: 'created_at', deletedAt: 'deleted_at', }) export class ChargeEntity extends Model<ChargeAttributes, ChargeCreationAttributes> implements ChargeAttributes {..} /* * node.js 의 Sequelize 라는 ORM에서는 @Table 에 이렇게 다양한 옵션이 존재해서 이런 차이가 있구나~~ 했었습니다. * 개인적으로는 이렇게 테이블에 옵션 때려박아두는 것 보다는 어노테이션(=데코레이터)을 여러개 사용하는 것이 낫다고 생각했습니다 */  @Id 와 @GeneratedValuehttps://jakarta.ee/specifications/persistence/3.2/apidocs/jakarta.persistence/jakarta/persistence/idhttps://jakarta.ee/specifications/persistence/3.2/apidocs/jakarta.persistence/jakarta/persistence/generatedvalue@Id @GeneratedValue(strategy = GenerationType.IDENTITY) // mysql auto_increment @Column(name = "report_id") /* column mapping */ var id: Long? = null /* spring에서 null로 보내면, DB에서 auto_increment */둘 다 엔티티 정의에서 기본키를 정의할 때 사용합니다. @Id는 말 그대로 기본키라는 것을 식별하기 위해 사용합니다. 함께 사용하는 GeneratedValue는, 기본키에 대한 생성 전략을 정의합니다. (전략 타입은 아래 표)https://jakarta.ee/specifications/persistence/3.2/apidocs/jakarta.persistence/jakarta/persistence/generationtype이번 수업에서는 db 에 기본키 생성을 위임하는 GenerationType.IDENTITY 를 사용하였습니다. // Sequelize PK 정의 예시 @Column({ field: 'charge_id', primaryKey: true, autoIncrement: true, type: DataType.INTEGER, }) id: number; /* * 여긴 없지만 @PrimaryKey 라는 데코레이터가 존재합니다. @Id와 같은 역할을 할 듯,, * autoIncrement: true, 외에 다른 옵션이 없었는데, @GeneratedValue 는 컨트롤할수 있는 범위가 넓은 것 같아 좋았습니다. */ @Columnhttps://jakarta.ee/specifications/persistence/3.2/apidocs/jakarta.persistence/jakarta/persistence/column@Column(name = "phone", nullable = false) /* column mapping */ var phone: String = phonename 은 말 그대로 물리 컬럼명을 이어주는 역할을 합니다. 사실 모든 경우에 대해, @Column을 해줘야 하는 줄 알았는데요, (DB에서 스네이크케이스, spring에서는 카멜케이스 )강의에서 "알아서 바꿔준다" 하고 슥 넘어간 거 같아 궁금해서 찾아보았습니다. By default, Spring Boot configures the physical naming strategy with CamelCaseToUnderscoresNamingStrategyhttps://docs.spring.io/spring-boot/how-to/data-access.html이렇게 스프링 부트에 내장되어 있는 전략에 영향을 받는 것으로? 우선 이해를 해 보았습니다.스프링이 참 딥해서,, 문서같은걸 뒤적거려도 확신이 잘 안서네요 ;ㅁ; nullable 은 default 가 true 이기 때문에 필요한 경우에만 명시해주면 됩니다!맨 위에 링크한 문서에, 각종 옵션들에 대한 default 가 나와있으니 개발할때 참고하면 좋을 것 같습니다.@Enumeratedhttps://jakarta.ee/specifications/persistence/3.2/apidocs/jakarta.persistence/jakarta/persistence/enumerated@Enumerated(value = EnumType.STRING) var category: ReportType = ReportType.valueOf(category)말 그대로, enum 타입이어야 함을 명시합니다. (옵션은 아래 표) https://jakarta.ee/specifications/persistence/3.2/apidocs/jakarta.persistence/jakarta/persistence/enumtype연관관계@ManyToManyhttps://jakarta.ee/specifications/persistence/3.2/apidocs/jakarta.persistence/jakarta/persistence/manytomany다대다(n-m) 관계일 때 사용합니다.n-m 관계의 경우 아래 사진처럼 1-n / n-1 이렇게 분리하는게 약간 >국룰< 입니다.문서에서도 @Jointable 을 사용해 중간 테이블을 지정하라고 되어 있습니다.https://jakarta.ee/specifications/persistence/3.2/apidocs/jakarta.persistence/jakarta/persistence/jointablemany-to-many는 정말 은근,, 사용할 일이 없었던 전적이 있어서 슥 넘어가겠습니다. @ManyToOne @OneToManyhttps://jakarta.ee/specifications/persistence/3.2/apidocs/jakarta.persistence/jakarta/persistence/onetomanyhttps://jakarta.ee/specifications/persistence/3.2/apidocs/jakarta.persistence/jakarta/persistence/manytoone@OneToMany( targetEntity = Post::class, fetch = FetchType.LAZY, cascade = [CascadeType.ALL] ) @JoinColumn(name = "user_id") var post: MutableList<Post> = mutableListOf()일대다 관계를 명시하는 어노테이션입니다. 관계를 맺고 있는 양쪽 테이블에 항상 모두 쓸 필요는 없고, 데이터가 필요한 부분에만 사용합니다. 이런 어노테이션의 경우 @OneToMany 앞(One)에 있는게 나고 타겟이 뒤(Many) 에 합니다.위 같은 코드가 User Entity 클래스 내부에 작성된 내용이라면, User(1) - UserTime(N) 입니다. 개인적으로 ORM의 관계는 요걸 헷갈리지 않는거부터 시작이라고 생각합니다..fetch 는 엔티티에서 항상 연관된 테이블의 정보를 가져올지 아닐지 선택합니다. (N+1 관련해서는 나중에 정리하겠습니다)cascade는 참조 무결성을 위한 키워드입니다. CascadeType.ALL 은 아래 표의 나머지들을 모두 적용하겠다는 뜻입니다.The value cascade=ALL is equivalent to cascade={PERSIST, MERGE, REMOVE, REFRESH, DETACH}.https://jakarta.ee/specifications/persistence/3.2/apidocs/jakarta.persistence/jakarta/persistence/cascadetype // Sequelize 관계 정의 예시 // user 테이블 @HasMany(() => PostEntity) posts: PostEntity[]; // post 테이블 @BelongsTo(() => UserEntity) user: UserEntity; @OneToOnehttps://jakarta.ee/specifications/persistence/3.2/apidocs/jakarta.persistence/jakarta/persistence/onetoone@OneToOne( targetEntity = UserTime::class, fetch = FetchType.LAZY, cascade = [CascadeType.ALL] ) @JoinColumn(name = "user_id") lateinit var timeInfo: UserTime일대일 관계를 명시할 때 사용합니다. user와 user가 사용한 시간인 userTime이 1-1 관계를 맺고 있는 경우의 예시입니다.  // Sequelize 관계 정의 예시 // user 테이블 @HasOne(() => UserTimeEntity) userTime: UserTime; // userTime 테이블 @BelongsTo(() => UserEntity) user: UserEntity; @JoinColumnhttps://jakarta.ee/specifications/persistence/3.2/apidocs/jakarta.persistence/jakarta/persistence/joincolumn조인의 기준? 되는 컬럼을 지정합니다.  프로젝트데이터베이스 관련해서 정의를 추가하였습니다. 근데 이번에 블로그 쓰면서 잘못 작성하고 그런걸 좀 찾아서 다시 해야할거같습니다. ....  

backend

표수진

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

벌써 스터디 2주차가 끝나다니 시간이 정말 빠른 것 같다. 2주차가 되니 피그마 사용하는 것도 그렇고 강의 듣고 실습하는 것도 적응해가면서 시간이 조금은 덜 걸릴 줄 알았는데 여전히 미션을 진행하는데 꽤 오랜 시간이 걸렸다 ㅠㅠ이번주는 지난주의 입력 컴포넌트에 이어서 계속해서 여러 종류의 컴포넌트를 만들어 가는 과정이였는데 오랜만에 디자인 작업을 하는 것 자체가 너무 재미있었던 것 같다. 특히 예전에 회사를 다니면서 UI디자인 작업을 했을 때, 이런 식으로 컴포넌트에 배리어블을 활용해서 디자인 했으면 얼마나 더 효율적이였을까 하는 생각이 계속해서 들었던 것 같다. [2주차 강의 내용 요약]1) 컴포넌트에 대한 기본 개념 다시 한번 정리컴포넌트 = 붕어빵 틀, 인스턴스 = 붕어빵 재료 (재료에 따라 다양한 붕어빵을 만들 수 있다)컴포넌트 만드는 순서싱글 컴포넌트 만들기 - 인스턴스(복제본)으로 테스트 - 베리언트 프로퍼티 - 테스트Property 먼저 적용 : 불린 프로퍼티, 텍스트 프로퍼티, 인스턴스 스왑 배리언트 프로퍼티는 보통 state별 -> Type(Style)별 -> Size별 2) 여러 컴포넌트 만들기입력 컴포넌트 나머지 만들기Label & Control Group, Text Field, Text Area, Select & Dropdown Menu 디스플레이 컴포넌트 만들기Avata, Accordion, Badge, Tooltips, Divider, Chips, Basic card, Table피드백 컴포넌트 만들기Alert, Toast, Skeleton Loader & Loading Spinner, Progress bar, Modal & Slot이번주에 다양한 컴포넌트 실습을 진행하면서 가장 크게 느낀 것은 이전 단계에 진행했었던 배리어블과 파운데이션이 잘 정리되어야 컴포넌트를 만들때도 문제없이 만들 수 있다는 것을 느꼈고, 새로운 컴포넌트를 제작할 때도 모든 부분을 하나하나 새로 만들기보다 만들어뒀던 기존의 컴포넌트를 또 활용해서 제작하는 것을 보고 이런 부분에서 디자인 시스템이 잘 만들어진다면 디자인 작업하는데 있어서 훨씬 많은 시간을 줄일 수 있고 효율적으로 일할 수 있을 것 같다는 생각이 들었다.특히 슬롯 컴포넌트를 활용해서 모달을 만드는 파트는 기존에 생각해보지 못한 방식으로 컴포넌트를 만들면서 놀라우면서도 실제로 활용하기 좋은 방법이라고 생각했다. 3) 온라인 특강 : chatGPT를 활용한 디자인 시스템 문서화이번주 주말에는 특강으로 디자인시스템 문서화에 대한 부분을 설명해주셨다. 디자인 시스템을 문서화해야 하는 이유부터 디자인 시스템 문서에 들어가야 하는 요소들(그 중에서도 꼭 필요하다고 생각하는 요소), gpt를 활용한 디자인 시스템 구축 방법을 배웠는데 내가 생각했던 부분보다 구체적인 내용으로 정리를 해줘서 아예 디자인 시스템 문서에 대한 개념이 부족할 때 길잡이로 많이 참고할 수 있을 것 같았다. 요즘에는 디자인 프로세스에 다양한 AI기술을 적용하는 방법들이 많던데 해당 부분에 대해서도 많은 공부가 필요해보인다! [이번주에 대한 회고]스스로 칭찬하고 싶은 점이번주에도 밀리지 않고 2주차 강의와 실습을 잘 끝냈다.아직은 강의를 듣고 실습 따라하기에 급하긴 했지만 지난주보다는 컴포넌트 제작 프로세스를 잘 따라가면서 만들었던 것 같다. 강의를 들으면서 최근에는 잘 사용하던 어플 화면을 볼 때도 '이 부분은 컴포넌트를 이렇게 나눠서 제작하지 않았을까?' '이 서비스의 디자인 시스템은 어떻게 작업되어 있지?'하고 화면을 분석하면서 보게 되는 게 예전보다 디자인 시스템에 대한 넓은 시야를 가질 수 있는 계기가 되는 것 같아 조금은 뿌듯했다. 아쉬웠던 점, 보완하고 싶은 점이번주에 알려주신 디자인 시스템 플러그인을 사용해서 기존에 만들었던 컴포넌트들도 문서화 해봐야겠다.실습 미션 중에 수정 보완 사항이 생겼었는데 다음주는 보다 꼼꼼하게 잘 따라서 미션을 진행해봐야겠다.현재 이직준비랑 피그마 스터디를 같이 진행하고 있어서 포트폴리오 준비랑 디자인 시스템 공부에 대한 시간을 효율적으로 잘 계획해서 진행해야 할 것 같다. 

UX/UIUXUI프로덕트디자인피그마figma이직준비

sodychoe

발자국 2주차 [인프런 워밍업 클럽 2기 백엔드(클린코드/테스트)]

이번 주 강의 섹션6섹션 5에 이어서 지뢰게임 도메인 계속 리펙터링 중주석주석은 추상화를 해칠 수 있는 위험요소가 있다주석도 버전 관리의 대상이다. 업데이트되지 않은 주석은 클라이언트를 혼란스럽게 할 수 있다추상화 가능한 부분을 주석으로 대체하고 있지 않은 지 고민해보자패키지패키지 이름도 문맥에 대한 정보를 가질 수 있다"Level" 패키지의 클래스는 "BegineerLevel" 이 아닌 "Begineer" 라고 써도 의미가 전달된다패키지는 적정 수준으로 분리해야 된다 너무 많으면 관리가 어렵고 너무 적으면 응집도가 낮아진다  버그 잡기, 알고리즘 개선게임 종료조건 버그 수정게임 보드의 크기가 커지면 stackoverflowerror 발생하는 부분을 알고리즘 변경으로 개선  섹션7스터디카페 이용권 판매라는 새로운 도메인에서 리펙토링 실습을 계속하였고 강의를 보기 전에스스로 리펙토링을 진행하면서 강의를 볼 때 중점적으로 볼 부분을 정리했다  섹션 7의 리펙토링 내용중복 제거, 메서드 추출객체에 메시지 보내기IO 통합일급 컬렉션display 로직 이관 - display 는 다양한 플랫폼에서 이뤄질 수 있으므로 그 동작을 객체가 하는 것보다 입출력을 담당하는 곳에서 하는 게 적당하다passOrder 개념 추출 -중요한 도메인 로직을 입출력에서 담당하지 말고 별도의 도메인 객체로 추상화하자 FileReader 인터페이스를 Provider 인터페이스를 도입하여 분리 (이 부분이 정말 대단하다!) 섹션8능동적 읽기난해한 코드를 적극적으로 리펙토링하면서 읽는 습관을 가져보자오버 엔지니어링필요한 적정 수준보다 더 높은 엔지니어링클린코드에 대한 모든 원칙, 조언은 꼭 필요한 상황에서만 사용하자은탄환은 없다클린 코드는 은탄환이 아니다결과물의 완성도가 중요한지 빨리 결과를 내는 게 중요한 지 나의 목표를 잘 생각하자적정 수준을 항상 지키자, 적정 수준을 알기 위해선 많이 사용하고 과하게 사용해보자섹션9마무리추상과 구체를 넘나드는 사고를 하자우리는 전지전능한 신이 아니기 때문에 한번에 모든 구조를 파악하고 설계할 수 없다 이상한 고정관념에 빠지지 말자 강의 회고섹션 7이 지금까지 배운 것을 적용할 수 있는 기회였어서 많이 기억에 남는다강의와 중간점검에서 멘토님이 하신 것과 똑같이 변경한 부분도 있었지만 멘토님이 인터페이스의 책임을 생각해서다시 리펙토링하는 것을 보고 아직 부족함을 깨닫고 더 많이 공부해야겠다고 생각이 들었다 이번 주 미션[섹션 7. 리팩토링 연습]의 "연습 프로젝트 소개" 강의를 보고, '스터디 카페 이용권 선택 시스템' 프로젝트에서 지금까지 배운 내용을 기반으로 리팩토링을 진행해 봅시다.오늘 1차 리팩토링을 마치고, 다음날 자고 일어나서 다시 한번 내가 리팩토링한 코드를 살펴봅니다.자고 일어나서 뇌가 맑아지면 새로운 시야가 열릴 때가 많거든요.만약 추가로 수정하고 싶은 부분이 보인다면, 2차 리팩토링을 진행합니다. 일단 결과적으로 StudyCafePssMachine.run 의 중복되는 코드를 리펙터링 하는 것이 가장 중요한 목표인 것 같았다  public void run() { try { outputHandler.showWelcomeMessage(); outputHandler.showAnnouncement(); outputHandler.askPassTypeSelection(); StudyCafePassType studyCafePassType = inputHandler.getPassTypeSelectingUserAction(); if (studyCafePassType == StudyCafePassType.HOURLY) { StudyCafeFileHandler studyCafeFileHandler = new StudyCafeFileHandler(); List<StudyCafePass> studyCafePasses = studyCafeFileHandler.readStudyCafePasses(); List<StudyCafePass> hourlyPasses = studyCafePasses.stream() .filter(studyCafePass -> studyCafePass.getPassType() == StudyCafePassType.HOURLY) .toList(); outputHandler.showPassListForSelection(hourlyPasses); StudyCafePass selectedPass = inputHandler.getSelectPass(hourlyPasses); outputHandler.showPassOrderSummary(selectedPass, null); } else if (studyCafePassType == StudyCafePassType.WEEKLY) { StudyCafeFileHandler studyCafeFileHandler = new StudyCafeFileHandler(); List<StudyCafePass> studyCafePasses = studyCafeFileHandler.readStudyCafePasses(); List<StudyCafePass> weeklyPasses = studyCafePasses.stream() .filter(studyCafePass -> studyCafePass.getPassType() == StudyCafePassType.WEEKLY) .toList(); outputHandler.showPassListForSelection(weeklyPasses); StudyCafePass selectedPass = inputHandler.getSelectPass(weeklyPasses); outputHandler.showPassOrderSummary(selectedPass, null); } else if (studyCafePassType == StudyCafePassType.FIXED) { StudyCafeFileHandler studyCafeFileHandler = new StudyCafeFileHandler(); List<StudyCafePass> studyCafePasses = studyCafeFileHandler.readStudyCafePasses(); List<StudyCafePass> fixedPasses = studyCafePasses.stream() .filter(studyCafePass -> studyCafePass.getPassType() == StudyCafePassType.FIXED) .toList(); outputHandler.showPassListForSelection(fixedPasses); StudyCafePass selectedPass = inputHandler.getSelectPass(fixedPasses); List<StudyCafeLockerPass> lockerPasses = studyCafeFileHandler.readLockerPasses(); StudyCafeLockerPass lockerPass = lockerPasses.stream() .filter(option -> option.getPassType() == selectedPass.getPassType() && option.getDuration() == selectedPass.getDuration() ) .findFirst() .orElse(null); boolean lockerSelection = false; if (lockerPass != null) { outputHandler.askLockerPass(lockerPass); lockerSelection = inputHandler.getLockerSelection(); } if (lockerSelection) { outputHandler.showPassOrderSummary(selectedPass, lockerPass); } else { outputHandler.showPassOrderSummary(selectedPass, null); } } } catch (AppException e) { outputHandler.showSimpleMessage(e.getMessage()); } catch (Exception e) { outputHandler.showSimpleMessage("알 수 없는 오류가 발생했습니다."); } }딱 보기에도 난잡한 조건문과 중복 로직이 보인다 이를 적절하게 공통 로직으로 묶고 추상화하려고 시도했다  public void run() { try { outputHandler.showAnnouncementMessageAtFirst(); StudyCafePassType passTypeSelectingUserAction = inputHandler.getPassTypeSelectingUserAction(); List<StudyCafePass> selectablePassesForUserSelection = studyCafeInitHandler.getSelectablePassesForUserSelection( passTypeSelectingUserAction); outputHandler.showPassListForUserSelection(selectablePassesForUserSelection); StudyCafePass selectedPassForUser = inputHandler.getSelectPass(selectablePassesForUserSelection); StudyCafeLockerPass selectableLockerPassForUserSelection = studyCafeInitHandler.getSelectableLockerPassForUserSelection( selectedPassForUser); boolean isLockerPurchased = isLockerPurchasedIfExists(selectableLockerPassForUserSelection); if (isLockerPurchased) { outputHandler.showPassOrderSummaryIfLockerPass(selectedPassForUser, selectableLockerPassForUserSelection); } outputHandler.showPassOrderSummary(selectedPassForUser); } catch (AppException e) { outputHandler.showSimpleMessage(e.getMessage()); } catch (Exception e) { outputHandler.showSimpleMessage("알 수 없는 오류가 발생했습니다."); } } 최종 코드는 다음과 같고 내가 시도해본 것은 다음과 같다StudyCafeFileHandler 에서 csv 파일을 불러오는 경로를 매직 스트링으로 변경StudyCafeFileHandler 를 구현하는 상위 인터페이스 initHandler 를 생성강의와 중간점검 피드백을 보면 다형성은 좋았는데 추상화의 목적을 잘못 설정했다묻지 말고 시켜라 원칙과 비슷한 결이라고 생각되는데 데이터를 가져오는 방법을 추상화하는 것보다는필요한 데이터 요구하는 메시지를 추상화하는 게 응집도 면에 더 낫다 인터페이스를 추상 클래스로 변경했다 그 이유는 pass 저장된 csv 파일을 읽는 로직을 public 으로 제공하지 않고 protected 로 사용하고 사용자가 사용가능한 pass 의 결과만 클라이언트가 이용하도록 하고 싶었다 이렇게 하고 싶었던 것은 내가 FileHandler 를 로직이 시작될 때 필요한 데이터를 초기화하는 역할로 생각했었는데너무 많이 추상화시킨 것 같다. 강의를 보고 많은 공부가 되었다 일부 메시지를 수정했다 studyCafeInitHandler.getSelectableLockerPassForUserSelection 같이 명확하게 하려고 했다null 처리를 하고 싶어서 Optional 을 이용하여 lockerPass 가 null 이면 isLockerPurchased 를 불리언으로 해서오버로딩된 showPassOrderSummary 를 쓰도록 했다 근데 멘토님을 보니까 lockerPass 자체를 Optional 로 사용하셨다 조금 더 생각해볼 걸 그랬다미션을 수행하면서 InputHandler 와 OutputHandler 가 따로 있는 게 코드를 읽을 때 난잡하다고 느껴졌는데 멘토님은 이를 하나의 클래스로 합치셔서 인상적이었다 습관적으로 입력 클래스와 출력 클래스를 분리했는데 꼭 그럴 필요는 없다는 걸 배웠다   

tjrmsduq

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

강의 내용Section 6 : 코드 다듬기주석의 양면성에 대해 배우면서 불필요한 주석을 줄이고 좋은 주석을 작성하는 방법을 배울 수 있었다.변수와 메서드의 나열 순서에 대하여 2가지 방법을 배웠는데 확실히 public 메서드와 private 메서드를 따로 분리하여 나열하는 것이 가독성이 좋아보였다.패키지를 의미 단위로 나누는 방법을 배웠는데, 먼저 코드를 작성하기 전에 필요하다고 느낄 수 있었다.버그를 찾아내고 더 효율적인 stack을 활용하는 방법을 알 수 있었다.SonarLine를 설치했는데 확실히 리팩토링 하는데 도움이 되는 것 같다. Section 7 : 리팩토링 연습스터디 카페 이용권 선택 시스템을 리팩토링하는 과정이다. 미션리팩토링 포인트에 맞추어 스터디 카페 이용권 선택 시스템을 리팩토링하는 미션이다. 회고이번 주차에서는 과제가 생각보다 어려웠다. 이전 강의들을 들으면서 확실히 이해한 부분은 많았는데 막상 실제로 사용해보려니 막막했다. 응용이 생각보다 힘들었다. 포인트에 맞춰서 차근차근 리팩토링했지만 어려운 부분이 많았다. 이후 강의를 통해 다시 한 번 리팩토링 해보았고 여러 번 강의를 돌려 보다가 회고 쓰는 것을 까먹고 늦게 작성했다.. 다음엔 아슬아슬하게 일요일이 아닌 금, 토 중에 작성해야겠다.  

dd

발자국 2주 차

본 내용은 인프런 워밍업 클럽 스터디를 진행하면서 작성한 회고 글 입니다.강의: Readable Code: 읽기 좋은 코드를 작성하는 사고법 학습 정리이번 주는 주석, 패키지, IDE의 도움을 받아서 코드를 다듬는 방법을 배웠다. 새로 알게 된 점은 IDE에서 editorconfig 파일을 만들어 스타일을 지정하는 것이였는데, 나중에 팀 프로젝트를 할 때 만들어두고 시작하면 좋을 것 같았다. 주석 관련해서도 기존에는 최대한 주석을 남기지 않아야 한다는 생각이었는데, 코드에 녹이지 못하는 의사 결정의 히스토리같은 주석은 도움이 될 수 있다는 것도 알게 되었다.미션이번 주 미션은 스터디카페 코드를 리팩토링하는 것이었다. 기존에 배웠던 여러가지 기법들을 활용해서 리팩토링을 해볼 수 있는 시간이었다. 기존에 리팩토링을 어떤 순서로 진행을 해야하는지 정리를 해놨기 때문에, 쉽게 진행이 될 거라고 생각했었는데 생각보다 오래 걸리고 어려웠다. 회고2주 동안 새로 알게된 점이 정말 많고, 또 실제로 미션을 진행해보면서 혼자서 리팩토링을 하는 과정이 오래걸린다는 걸 느꼈기 때문에, 많이 연습을 해야겠다는 생각이 들었다. 다음 주 부터 시작되는 테스트 코드도 열심히 듣고, 우빈님이 라이브에서 추천해주신 책도 구매해서 봐야겠다는 생각이 들었다. 

dd 1개월 전
Yeoonnii

[인프런 워밍업 클럽 CS 2기] 2주차 발자국 - 둘째 주 미션

[ 운영체제 ]Q1. FIFO 스케줄링의 장단점이 뭔가요?A1.장점 : 단순하고 직관적이며, 순차적으로 처리 되기 때문에 일괄처리 시스템에 적합하다.단점 :먼저 들어온 프로세스가 끝나야만 다음 프로세스가 실행될 수 있다.실행시간이 짧고 늦게 도착한 프로세스가 실행시간이 길고 빨리 도착한 프로세스의 작업을 기다려야 한다.I/O 작업이 있는 경우 I/O 작업이 끝날때 까지 CPU 가 쉬게 되므로 CPU 사용률이 떨어진다.Q2. SJF를 사용하기 여러운 이유가 뭔가요?A2. Burst Time이 짧은 프로세스를 우선적으로 실행 시, 상대적으로 Burst Time이 긴 작업은 실행될 기회를 얻지 못하고 계속 대기할 수 있게 되며 프로세스의 종료시간을 예측하기 어려울 수도 있다. 이러한 이유로 SJF 알고리즘은 실제적으로 사용하지 않는다.Q3. RR 스케줄링에서 타임 슬라이스가 아주 작으면 어떤 문제가 발생할까요?A3. 컨텍스트 스위칭이 자주 발생하여 타임 슬라이스에서 실행되는 프로세스의 처리량 보다 컨텍스트 스위칭을 처리하는 양이 훨씬 커진다. 이러한 상황을 ‘오버헤드가 커진다’고 한다.Q4. 운영체제가 MLFQ에서 CPU Bound Process와 I/O Bound Process를 어떻게 구분할까요?A4.CPU를 사용하는 프로세스가 실행하다가 스스로 CPU를 반납하면 CPU 사용이 적은거니 I/O Bound Process 일 확률이 높다.CPU를 사용하는 프로세스가 타임 슬라이스 크기를 오버해서 CPU 스케줄러에 의해 강제로 CPU를 뺏기는 상황이면 CPU 사용이 많은 것이니 CPU Bound Process일 확률이 높다.Q5. 공유자원이란무엇인가요?A5. 프로세스 통신시 공통으로 이용하는 변수나 파일을 의미 한다. 공유 자원은 프로세스의 접근 순서에 따라 결과가 달라질 수 있다.Q6. 교착상태에 빠질 수 있는 조건은 어떤 것들을 충족해야할까요?A6.상호배제 : 어떤 프로세스가 한 리소스를 점유 했다면, 해당 리소스는 다른 프로세스에게 공유 되면 안된다.비선점 : 프로세스 A가 리소스를 점유하고 있을 때, 다른 프로세스는 해당 리소스를 빼앗을 수 없다.점유와 대기 : 어떤 프로세스에서 리소스 A를 가지고 있는 상태에서 리소스 B를 원하는 상태여야 한다.원형 대기 : 점유와 대기를 하는 프로세스들의 관계가 원형을 이루고 있어야 한다.[ 자료구조와 알고리즘 ]Q1. 재귀함수에서 기저조건을 만들지 않거나 잘못 설정했을 때 어떤 문제가 발생할 수 있나요?A1. 기저조건이 없으면 함수는 자기자신을 계속해서 호출하게 되므로 무한루프에 빠지게 되며 스택 오버플로우(Stack Overflow)가 발생한다.Q2. 0부터 입력 n까지 홀수의 합을 더하는 재귀 함수를 만들어보세요.A2.function sumOdd(num){ if(num <= 0) return 0; // 기저조건 if(num % 2 === 1){ // 홀수인 경우 return num + sumOdd(num - 2); // 다음 홀수를 인자로 재귀함수 호출 } else { // 짝수인 경우 return sumOdd(num - 1); // 다음 홀수를 인자로 재귀함수 호출 } } console.log(sumOdd(10)) // 25

알고리즘 · 자료구조인프런워밍업클럽자료구조알고리즘CS

sdsd988

[워밍업 클럽 스터디 2기 백엔드(클린코드, 테스트코드)] 2주차 발자국

워밍업 클럽 스터디 2기 백엔드 2주차 발자국 강의 내용요약 : 1주차에 배운 내용을 바탕으로 읽기 좋은 코드를 작성해보자.. Section 5 - 객체 지향 적용하기Section 6 - 코드 다듬기Section 7 - 리팩토링 연습  강의 내용Value Object값 객체 활용상대 위치, 셀 등 기존의 원시값을 객체 값으로 리팩토링 하는 과정이번 강의에서 가장 어려웠고, 실무에 어떻게 적용할 수 있을지 고민 되었던 강의반복 되거나, 대체 될 수 있는 값을 객체로 재정의하고 기존 코드를 리팩토링하는 과정인데, 간단할 거라고 생각했는데 생각보다 어려웠다. 미션스터디카페 회원 시스템 리팩토링  회고이번 주차의 핵심은 실습 이라고 생각이 들었다. 이론과 실제는 다르다는 것을 느끼는 한 주였다.1주차 강의를 들으면서 강의 내용을 스스로 이해 했다고 생각했다.막상 2주차 미션인 스터디카페 코드 리팩토링을 수행하면서, 부족함을 느꼈다.부족하다고 생각하는 부분은 1주차 강의 내용에 갇혀있다는 것다시 말하면, 스터디카페 미션을 수행하면서 나의 생각과 고민이 아닌 1주차에 했던 내용을 단순 반복하고 있다는 느낌이 들었다. 스스로 응용 하지 못하고 있다는 생각이 들었다.이러한 이유에는 여러 요인이 있겠지만, 가장 중요한 것은 내가 시간 분배를 잘 하지 못했던 것 때문이라고 생각한다.미션 수행에 더 많은 시간을 할애했어야 했는데, 사실 강의 따라 듣기도 바빴다. 

채널톡 아이콘