블로그

강현

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

강의 수강섹션3. 단위 테스트이전에 프로젝트에서도 사용해본 적이 있는 단위테스트와 JUnit에 대해 배웠다. 해본 적이 있지만, 다시 막상 보니 새롭게 느껴져서 복습하듯이 학습했다.테스트하기 어려운 영역에 대해, 이전에는 해당 테스트는 그냥 사람이 직접 테스트하는 것으로 구현했었다. 하지만 이번 강의를 통해 밖으로 파라미터를 주입시켜주어 순수 함수로 만들어주면, 테스트 코드를 작성할 수 있다는 것을 알게 되었다.섹션4. TDDTDD를 이론으로만 공부해봤지 직접 코드를 작성하는 것은 처음이었다. 이론으로만 배웠을 때는 너무 멀게만 느껴졌는데, 직접 코드를 따라치니까 더 TDD가 무엇인가에 대한 개념이 가까워지는 것 같다.TDD 코드를 따라치면서 느낀 것이, TDD를 지키지 않고 개발할 때보다 코드 파일들을 많이 왔다갔다 하는 것이라고 체감이 들었다. TDD를 지킬 때에는 내가 무엇을 개발하고 있었는가에 대해 계속 의식해주어야 될 것같다는 생각이 들었다. 강의를 들을 때에는 사용하지 않았지만, 내가 직접 TDD를 스스로 한다면, Red Green refactor을 하는 과정에 주석에 todo 표시를 자꾸 해줘야 되겠다.섹션5. 테스트는 [ ]다.👍 DisplayName을 "ㅇㅇ 테스트"라고 명명하는 습관이 있었는데, 강의 시작하자 마자 지적받아서 뜨끔했다. 이제 절대로 저렇게 작명하지 않을 것이다.👍 given, when, then을 나누어 작성하는 BDD에 대해 배웠다. 아직 given, when, then에 어떤 명령어가 해당 되는지 헷갈려서 익숙치 않다. 계속 연습해봐야겠다.섹션6. Spring & JPA 기반 테스트👍 @SpringBootTest와 @DataJpaTest 의 가장 큰 차이는 @DataJpaTest는 @Transactional을 포함하고 있다는 것이다.@SpringBootTest에 @Transactional 추가해주면, @AfterEach 함수로 데이터 초기화 안해줘도 되는데 이러면 서비스 클래스에 @Transactional이 안붙어있어도 테스트코드가 통과되는 문제가 있다. 이거 알고 쓰자.👍 서비스 클래스에 @Transactional(readonly = true) 를 걸고, CUD 메소드에 한해서 @Transactional 을 해주자.👍 ㅇㅇ 생성 함수에서는 동시성을 고려해주어야 한다.함수가 호출되는 빈도수가 낮으면? 👉 ㅇㅇ의 id에 unique 제약조건 걸고, 실패 시 재시도하는 로직을 추가함수 호출 빈도가 높으면? 👉 ㅇㅇ의 id를 +1씩 증가하는 값이 아니라, UUID같은 것으로 대체미션Day11역시 저번 실습 미션처럼 강의를 보며 따라칠때는 너무 쉬운 것 같은데 직접 치려니 힘들다. 특히 어떤 메소드에 대해 테스트코드를 작성해야하는가 판단하는 부분이 가장 어려웠다.이미 구현 코드는 작성되어 있으니 given, when, then을 나누어 DDD를 지켜 코드를 작성했다. 각 명령어들이 given, when, then 어느쪽에 속해있는지 판단하는 것이 아직 어렵다. 그래도 강의 맨 처음에 지적받은 DisplayName 명명 규칙은 열심히 지킨 것 같다.

백엔드

wonderson

[워밍업 클럽 3기 CS 전공지식] 3주차 자료구조와 알고리즘 미션

지금까지 배운 5개의 정렬 알고리즘의 장단점과 시간 복잡도를 적어주세요.버블 정렬앞에 있는 숫자와 옆에 숫자를 비교해서 자리를 바꾸는 알고리즘장점이해가 쉽고 구현이 간단합니다. 단점O(n^2) 으로 성능이 좋지 않습니다.시간 복잡도이중 for문이 핵심 로직으로 O(n^2) 입니다.선택 정렬정렬된 영역과 정렬되지 않은 영역을 나눠서 정렬되지 않은 영역을 비교하는 알고리즘장점이해가 쉽고 구현이 간단합니다. 단점O(n^2) 으로 성능이 좋지 않습니다.시간 복잡도이중 for문이 핵심 로직으로 O(n^2) 입니다.삽입 정렬정렬되지 않은 영역에서 데이터(가장 앞에 있는 숫자)를 하나씩 꺼내서 정렬된 영역 내 적절한 위치에 "삽입"해서 정렬하는 알고리즘장점이해가 쉽고 구현이 간단합니다. 단점O(n^2) 으로 성능이 좋지 않습니다.시간 복잡도이중 for문이 핵심 로직으로 O(n^2) 입니다.병합 정렬재귀를 이용해 정렬하는 알고리즘 (분할 정복) 장점 O(nlogn) 으로 성능이 좋습니다. 단점재귀적인 기법으로 이해하기 어렵습니다.구현이 어렵다.시간 복잡도각 단계를 거칠 때마다 영역의 수가 반으로 줄기 때문에 logn분할된 배열을 병합할 때는 n개의 데이터를 n번 비교=> n, nlogn 곱해서 O(nlogn) 성능이 나옵니다.퀵 정렬재귀를 이용해 정렬하는 알고리즘 (분할 정복)한 번 진행될 때마다 피벗이 정렬되고 정렬된 배열을 좌우로 나눠서 같은 방식으로 재귀 호출을 해 모든 배열을 정렬합니다. 장점 O(nlogn) 으로 성능이 좋습니다. 단점재귀적인 기법으로 이해하기 어렵습니다.구현이 어렵습니다.시간 복잡도피벗을 기준으로 반을 나눕니다. -> 수가 1개 남을 때까지 반으로 나눕니다. logn나눠진 단계를 배열의 원소 수 n만큼 진행=> n, nlogn 곱해서 O(nlogn) 성능이 나온다. 메모리가 부족한 시스템에서 어떤 문제를 해결하는데 재귀로 쉽게 구현이 가능할 것 같습니다. 여러분이라면 메모이제이션과 타뷸레이션 중 어떤 걸 이용하실 건가요? 이유를 함께 적어주세요.재귀로 쉽게 구현이 가능하다고 했으니 먼저 메모이제이션을 이용해서 문제를 해결할 거 같습니다.구현을 하고 실행했을 때 메모리를 어느 만큼 사용하는지 측정해보고 계속 사용할 지 판단하겠습니다.메모제이션으로 구현을 했는데 메모리가 부족하다면 그 때 타뷸레이션을 이용해 문제를 해결하겠습니다.   ※ 출처[인프런 / 그림으로 쉽게 배우는 자료구조와 알고리즘 (감자) / 섹션 3 (유닛 6~10)]

wonderson

[워밍업 클럽 3기 CS 전공지식] 3주차 운영체제 미션

메모리의 종류는 어떤것들이 있나요? 각 메모리의 특징도 함께 적어주세요.레지스터 가장 빠른 기억 장소 CPU 내에 존재합니다.컴퓨터의 전원이 꺼지면 데이터가 사라지기 때문에 휘발성 메모리라고 부릅니다.캐시레지스터와 메인메모리의 속도 차이를 해결해줍니다.메인메모리에 있는 값을 레지스터로 옮기려면 한참 걸리기 때문에 필요할 것 같은 데이터를 미리 가져옵니다.메인메모리운영체제와 다른 프로세스들이 올라가는 공간입니다.전원이 공급되지 않으면 데이터가 지워지기 때문에 휘발성 메모리라고 부릅니다.하드디스크(HDD)나 SSD 보다 속도는 빠르지만 가격이 비싸서 데이터를 저장하기 보다는 실행 중인 프로그램만 올립니다.보조저장장치 (HDD, SSD)사무용 프로그램이나 게임, 작업한 파일을 저장할 때 필요합니다.메인메모리에 비해 가격이 저렴하고 전원이 공급되지 않아도 데이터가 지워지지 않는 비휘발성 메모리 입니다.메모리에 접근하는 시간이 보조저장장치 쪽으로 갈수록 느려집니다. 사용자 프로세스가 메모리의 운영체제 영역에 침범하지 못하도록 만든 레지스터는 어떤 레지스터일까요?경계 레지스터CPU 내에 존재하는 레지스터로 메모리 관리자가 사용자 프로세스가 경계 레지스터의 값을 벗어났는지검사하고 만약 벗어났다면 해당 프로세스를 종료 시킵니다. 메모리 할당 방식에서 가변 분할 방식과 고정 분할 방식의 장단점은 뭔가요?가변 분할 방식장점메모리에 연속된 공간에 할당되기 때문에 낭비되는 공간이 없습니다. (내부 단편화가 없습니다.)프로세스 크기에 딱 맞게 할당됩니다.단점'외부 단편화'가 발생합니다.프로세스가 사용한 메모리를 반납 후 다른 프로그램이 메모리를 사용하려고 할 때반납된 메모리 2개 영역을 합치면 다른 프로그램이 메모리를 사용할 수 있는데 연속되지 않은 메모리 공간이라서 사용하지 못합니다.고정 분할 장식장점구현이 간단하고 오버헤드가 적습니다.같은 크기로 나누기 때문에 단순합니다.단점작은 프로세스도 큰 영역에 할당되어서 공간이 낭비되는 '내부 단편화'가 발생합니다. CPU 사용률을 올리기 위해 멀티프로그래밍을 올렸지만 스왑이 더 많이 이루어져 CPU 사용률이 0%에 가까워 지는 것을 뭐라고 할까요?스레싱해결 방법하드웨어적으로 해결메모리 크기를 늘립니다.소프트웨어적으로 해결프로세스가 실행되면 일정량의 페이지가 할당됩니다.Page Fault 가 발생하면 더 많은 페이지를 할당합니다.Page Fault 가 너무 적게 발생하면 메모리가 낭비되는 것이라고 판단해 페이지를 회수합니다.=> 프로세스가 실행되는 동안 해당 프로세스에게 맞는 적절한 페이지 수가 결정됩니다. HDD나 SSD는 컴퓨터를 실행시키는데 꼭 필요한 걸까요?이유를 함께 적어주세요.필요하다고 생각합니다.컴퓨터가 부팅되면 HDD나 SSD에 저장되어 있는 운영체제가 메모리에 올라오게 됩니다.만약에 운영체제 없이 컴퓨터만 실행 한다면 필요 없을 수도 있습니다.  파일을 삭제해도 포렌식으로 파일을 복구할 수 있는 이유가 무엇일까요?특정 파일을 삭제할 때 파일 시스템은 파일의 모든 정보를 지우는 것이 아닙니다.파일 테이블의 헤더를 삭제하고 free block list에 추가를 합니다.이렇게 처리하면 사용자는 파일이 삭제된 것처럼 느껴지지만 사용했던 블록의 데이터는 그대로 남아있습니다.그래서 데이터를 복구할 수 있습니다.  ※ 출처[인프런 / 그림으로 쉽게 배우는 운영체제 (감자) / 섹션 8~10]

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

강의수강recoilatom: 상태의 조각useRecoilState: useState hook과 유사하게 전역적으로 recoil 상태를 읽고 쓰는 데 사용useRecoilValue: recoil 상태를 읽는 데만 사용 react-intersection-observerintersercion Observer API를 react에서 사용하기 쉽도록 도와주는 라이브러리useInView 훅은 react-intersection-observer 라이브러리에서 제공하는 기능으로, 특정 요소가 화면에 보이는지(inView)를 감지하고, 해당 요소에 ref를 할당하여 추적할 수 있게 한다.threshold: 0 옵션은 요소의 0%라도 화면에 나타나면 inView가 true가 되도록 설정useInfiniteQuery const { data, isFetchingNextPage, isFetching, fetchNextPage, hasNextPage } = useInfiniteQuery({ initialPageParam: 1, queryKey: ["movie", search], queryFn: ({ pageParam }) => searchMovies({ search, pageSize, page: pageParam, favoriteCount, }), getNextPageParam: (lastPage) => { if (lastPage.data.length < pageSize) { return null; } return lastPage.page ? lastPage.page + 1 : null; }, });무한 스크롤 기능을 쉽게 구현할 수 있도록 해주는 hookinitialPageParam 으로 처음에 가져올 페이지 번호를 설정queryFn: -> searchMovies 함수를 호출해 데이터를 패치getNextPageParam: 다음 페이지 번호를 반환해 추가 데이터를 불러 오도록 함 조건에 따라 null을 반환해 무한 스크롤을 멈출 수 있게 해준다미션사용자가 특정 영화를 “찜”할 수 있도록 Supabase를 활용해 즐겨찾기 리스트 구현supabase movie 테이블에 favorite: boolean column 추가 기본 값: falseactions/move-actions.ts에 update action 코드 추가type MovieUpdate = Database["public"]["Tables"]["movie"]["Update"]; export async function updateMovie(movie: MovieUpdate) { const supabase = await createServerSupabaseClient(); const { error } = await supabase .from("movie") .update({ ...movie, }) .eq("id", movie.id); handleError(error); } 현재 Movie가 찜되어 있는지 나타내는 컴포넌트 MovieFavoriteIconfavorite boolean값을 받아 찜했으면 핑크색 하트를 찜 안되어있으면 빈하트를 렌더링export default function MovieFavoriteIcon({ favorite }: { favorite: boolean }) { return ( <> {favorite ? ( <i className="fa-solid fa-heart font-bold" style={{ fontSize: 25, color: "#ff9fd2" }} /> ) : ( <i className="fa-regular fa-heart" style={{ fontSize: 25, color: "#fff" }} /> )} </> ); } components/movie-card, movies/[id]/ui에 MovieFavoriteIcon 컴포넌트 추가import Link from "next/link"; import type { Movie } from "actions/movie-actions"; import MovieFavoriteIcon from "./movie-favorite-icon"; export default function MovieCard({ movie }: { movie: Movie }) { return ( <div className="col-span-1 relative"> <img src={movie.image_url} className="w-full" /> <Link href={`/movies/${movie.id}`}> <div className="absolute top-0 right-0 px-1.5 py-2"> <MovieFavoriteIcon favorite={movie.favorite} /> </div> <div className="absolute top-0 bottom-0 left-0 right-0 z-10 flex justify-center items-center bg-black opacity-0 hover:opacity-80 transition-opacity duration-300"> <p className="text-xl font-bold text-white">{movie.title}</p> </div> </Link> </div> ); }  "use client"; import { useState } from "react"; import { useMutation } from "@tanstack/react-query"; import { Movie, updateMovie } from "actions/movie-actions"; import { queryClient } from "config/react-query-client-provider"; import MovieFavoriteIcon from "components/movie-favorite-icon"; export default function UI({ movie }: { movie: Movie }) { const [isFavorite, setIsFavorite] = useState(movie.favorite); const updateMovieMutation = useMutation({ mutationFn: () => updateMovie({ ...movie, favorite: !isFavorite, }), onSuccess: () => { setIsFavorite((prev) => !prev); queryClient.invalidateQueries({ queryKey: ["movie"] }); }, }); return ( <div className="flex flex-col md:flex-row items-center"> <img src={movie.image_url} className="w-1/3 xl:w-1/4" /> <div className="w-full md:w-2/3 items-center md:items-start flex flex-col p-6 gap-4"> <div className="flex justify-between items-center w-full"> <h1 className="text-3xl font-bold">{movie.title}</h1> <div> <button className="bg-pink-50 border-2 border-pink-100 rounded-md px-1.5 py-1" onClick={() => updateMovieMutation.mutate()} > <MovieFavoriteIcon favorite={isFavorite} /> </button> </div> </div> <p className="text-lg font-medium">{movie.overview}</p> <div className="font-bold text-lg"> <i className="fas fa-star mr-1" /> Vote Average : {movie.vote_average} </div> <div className="font-bold text-lg">Popularity: {movie.popularity}</div> <div className="font-bold text-lg"> Release Date: {movie.release_date} </div> </div> </div> ); } movies/[id]/ui.tsx에서 해당 movie의 favorite을 설정하도록 mutate 추가찜한 영화를 영화 리스트 화면의 최상단으로 보여주도록 정렬위에서 추가했던 movie 테이블의 favorite 컬럼을 기준으로, 무한 스크롤 movie 요청시 찜한 영화를 우선해 가지고 오는 것을 목표로 했다.찜한 영화 favoriteMovie , 나머지 기본 영화 remainMovie 를 각각 가지고 오는 방식으로,예를 들어 favoriteMovie 가 6개이고 pageSize 가 12인 경우page = 1: favoriteMovie.length = 6 , remainMovie.length = 6page = 2: favoriteMovie.length = 0 , remainMovie.length = 12page = 3: favoriteMovie.length = 0 , remainMovie.length = 12favoriteMovie 가 13개이고 pageSize 가 12인 경우page = 1: favoriteMovie.length = 12 , remainMovie.length = 0page = 2: favoriteMovie.length = 1 , remainMovie.length = 11page = 3: favoriteMovie.length = 0 , remainMovie.length = 12favoriteMoive 를 먼저 pageSize 만큼 가지고 오고 favoriteMoive 의 수가 pageSize 보다 작은 경우 remainMovie 를 가지고 오는 방식이다.여기서 가장 중요한 것은 각각의 영화들의 range 다. 기존 page 와 pageSize 를 기준으로 range 를 정하는 경우 remainMovie 를 쿼리할 때 중간에 지나가는 경우가 생긴다. 위 2번 째 예의 page = 2일 때 remainMovie 의 범위는 0 ~ 10 이여야 한다. 이전 Movie들의 정보를 알아야 구할 수 있는데, 현재 코드에서는 이를 간단하게 구할 좋은 방법이 떠오르지 않았다. 따라서 supabase에 저장한 favoriteMovie 의 수를 미리 가지고와 이를 이용해 favoriteCount , page, pageNumber 세 개의 값들을 조합해 범위를 구했다.function getRange(favoriteCount: number, page: number, pageSize: number) { const start = (page - 1) * pageSize; // 현재 페이지의 시작 인덱스 const end = start + pageSize - 1; // 현재 페이지의 마지막 인덱스 const favStart = start < favoriteCount ? start : favoriteCount; const favEnd = Math.min(end, favoriteCount - 1); const favLength = Math.max(favEnd - favStart + 1, 0); const remStart = Math.max(0, start - favoriteCount); const remEnd = remStart + (pageSize - favLength) - 1; const remLength = Math.max(remEnd - remStart + 1, 0); return { favoriteRange: favLength > 0 ? [favStart, favEnd] : [], remainRange: remLength > 0 ? [remStart, remEnd] : [], }; } getRange 함수는 favoriteCount , page, pageNumber 세 개의 값들을 이용해 favoriteMovie , remainMovie 의 범위를 구해 반환하는 함수다.현재 페이지의 시작 인덱스 start 가 찜한 영화의 수 favoriteCount 보다 작은 경우 이번 페이지에서는 찜한 영화만을 가지고 올 거기에 favStart 가 start 가 된다. 크거나 같은 경우에는 찜한 영화가 부족함으로 favoriteCount 값으로 설정한다.favEnd = end 가 favoriteCount 보다 큰 경우, 존재하는 찜한 영화 개수까지만 가져온다.remStart = 찜한 영화 개수를 넘어서야 나머지 영화를 가지고 오므로 start - favoriteCount 가 0보다 큰 경우인지 확인하면 값을 설정remEnd = 찜한 영화 개수(favLength ) 를 채운 후 남은 공간만큼 나머지 영화를 가지고 온다최종 변경 코드actions/movie-actions.tsasync function searchMoviesByFavorite( search: string, range: number[], isFavorite: boolean ) { if (range.length === 0) { return []; } const [start, end] = range; const supabase = await createServerSupabaseClient(); const { data, error } = await supabase .from("movie") .select("*") .like("title", `%${search}%`) .eq("favorite", isFavorite) .order("id") .range(start, end); handleError(error); return data; } export async function searchMovies({ search, page, pageSize, favoriteCount, }: { search: string; page: number; pageSize: number; favoriteCount: number; }) { const { favoriteRange, remainRange } = getRange( favoriteCount, page, pageSize ); const [favoriteMovies, remainMovies] = await Promise.all([ searchMoviesByFavorite(search, favoriteRange, true), searchMoviesByFavorite(search, remainRange, false), ]); const data = [...favoriteMovies, ...remainMovies]; return { data, page, pageSize, hasNextPage: data.length === pageSize, }; } app/page.tsx// page.tsx import { createServerSupabaseClient } from "utils/supabase/server"; import UI from "./ui"; export const metadata = { title: "TMDBFLIX", description: "Netflix clone using TMDB API", }; export default async function Page() { const supabase = await createServerSupabaseClient(); const { count } = await supabase .from("movie") .select("*", { count: "exact", head: true }) .eq("favorite", true); return <UI favoriteCount={count} />; } // ui.tsx "use client"; import MovieCardList from "components/movie-card-list"; export default function UI({ favoriteCount }: { favoriteCount: number }) { return ( <main className="mt-14 mb-12"> <MovieCardList favoriteCount={favoriteCount} /> </main> ); } MovieCardList 컴포넌트에서 사용할 수 있도록 총 찜한 영화의 수를 쿼리해서 prop으로 전달components/Movie-card-list.tsxexport default function MovieCardList({ favoriteCount, }: { favoriteCount: number; }) { const search = useRecoilValue(searchState); const pageSize = 12; const { data, isFetchingNextPage, isFetching, fetchNextPage, hasNextPage } = useInfiniteQuery({ initialPageParam: 1, queryKey: ["movie", search], queryFn: ({ pageParam }) => searchMovies({ search, pageSize, page: pageParam, favoriteCount, }), getNextPageParam: (lastPage) => { if (lastPage.data.length < pageSize) { return null; } return lastPage.page ? lastPage.page + 1 : null; }, }); ... } 찜한 영화의 수 favoriteCount 를 useInfiniteQuery queryFn searchMovies 에 추가로 전달

한석환

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

📖3주차 학습내용1. Feedback 컴포넌트알림, 토스트는 Information, success, warning, error 등 시스템 상태를 고려한 컴포넌트 set를 만들어습니다.Spinner loader, Skeleton는 화면 대기 중에 나타나는 프로토타입을 고려한 컴포넌트 set을 만들었습니다.Modal은 사용 목적에 따라 다양하게 쓰이기 때문에 Slot 컴포넌트를 알게 되었으며, 하나의 모달 컴포넌트를 가지고 여러 형태를 만들 수 있었습니다.2. Navigation 컴포넌트Bottom, Side, Global 네비게이션을 만들 때 포함되는 메뉴 Item 컴포넌트를 Default, Hovered, Pressed, Selected 등 인터렉션된 상태를 고려하여 만들었습니다. 특히 Side는 다양한 디바이스 환경을 고려하여 여러 Type을 만들어야 했으며, Global은 선택했을 때 공개되는 하위 메뉴부터 알림, 프로필 같은 Utility적인 기능까지 고려해야 했죠.3. 배리어블 모드라이트/다크, 멀티 브랜드, 디바이스 별 반응형, 다중 언어 지원 등에서 apply variable mode만 변경하여 적용하는 방법에 대해 배웠습니다. 멀티 브랜드: 브랜드에 따른 컬러 및 타이포는 Primitive를 가지고 Theme 배리어블을 추가로 만들고, Semantic에 Theme 배리어블을 등록하여 컴포넌트나 디자인 요소에 적용시키기반응형디바이스에 따른 컴포넌트 set을 만들고, 각 이름을 가진 String 배리어블을 만들고 인스턴스 배리언트 프로퍼티에 연결시키기.컨텐츠 영역에선 Min Width와 constraints를 L+R을 적용해서 디바이스 크기 별로 바로 대응할 수 있게 설정하기.다중 언어:기존 컴포넌트를 가지고 새로운 컴포넌트 set 및 목적에 따른 배리언트 프로퍼티 만들기 -> String 배리어블 -> 인스턴스의 배리언트 프로퍼티에 배리어블 연결 -> 배리어블 모드 지정국가별 문화 및 언어습관을 고려하여 좌측 정렬, 우측 정렬, 이미지 및 텍스트 컨텐츠 등 변경이 가능함. ☺좋았던 점배리어블 모드 기능을 사용하려면 어떻게 Setting해야 하는지 배울 수 있었습니다.컴포넌트를 구성하는 Part 요소의 인터렉션을 고려하여 만들면서 컴포넌트 체계에 대해 자세히 알 수 있었습니다.😁잘한 점커리큘럼에 맞춰서 미루지 않고 정해진 할당량을 채우려고 노력했어요.특별히 잘한 점이 뭐가 있는지 기억이 안 납니다... 이번 주는 배울 게 많아서 특별히 더 잘하려고 노력하지 못했어요.😅아쉬운 점Responsive 파트에서 강의를 따라 만들었는데 잘 되지 않았습니다. 컴포넌트 내부에 fill, hug, min width 등 고려할 변수가 많은데 익숙하지 않아 자꾸 헷갈리더라구요. 컴포넌트의 구성 요소가 복잡해질수록 따라 만들기 버거웠습니다. 특히 네비게이션 컴포넌트는 내부에 들어갈 컴포넌트가 다양해서 영상을 여러 번 돌려봤어요.

UX/UI인프런워밍업클럽디자인시스템배리어블Figma볼드UX

jurjur

CS 미션 3주차

운영체제메모리의 종류는 어떤것들이 있나요? 각 메모리의 특징도 함께 적어주세요.레지스터가장 빠른 기억 장소로 CPU내에 존재.휘발성 메모리캐시레지스터와 메인 메모리 사이에 존재하는 메모리레지스터와 메인 메모리 데이터 속도의 차이 때문에 필요한 데이터를 미리 갖고와서 저장하는 공간메인 메모리실제 운영체제와 다른 프로세스가 올라가는 공간휘발성 메모리보조 저장 장치전원이 공급되지 않아도 데이터가 지워지지 않는 비 휘발성 메모리 2. 사용자 프로세스가 메모리의 운영체제 영역에 침범하지 못하도록 만든 레지스터는 어떤 레지스터일까요?경계 레지스터CPU내에 존재하는 레지스터로 메모리 관리자는 사용자 프로세스가 경계 레지스터의 값을 벗어 났는지 확인하고, 침범했다면 사용자 프로세스를 종료 시킴.메모리 할당 방식에서 가변 분할 방식과 고정 분할 방식의 장단점은 뭔가요?가변 분할 방식메모리에 연속된 공간에 할당 되기 때문에 낭비되는 공간인 내부 단편화가 없음외부 단편화가 발생고정 분할 방식구현이 간단하고 오버헤드가 적음작은 프로세스도 큰 공간에 할당 되면 내부 단편화 발생CPU 사용률을 올리기 위해 멀티프로그래밍을 올렸지만 스왑이 더 많이 이루어져 CPU 사용률이 0%에 가까워 지는 것을 뭐라고 할까요?스레싱HDD나 SSD는 컴퓨터를 실행시키는데 꼭 필요한 걸까요?실행이 가능하긴 하나 일반 메모리에 파일을 저장 및 운영체제를 저장하는 방식은 비효율적이라 HDD 또는 SSD가 필요하다.파일을 삭제해도 포렌식으로 파일을 복구할 수 있는 이유가 무엇일까요?파일이 직접 삭제되는게 아닌 Free Block List에 파일의 사용 공간이 등록되고 블록은 남아 있기 때문. 자료구조와 알고리즘지금까지 배운 5개의 정렬 알고리즘의 장단점과 시간 복잡도를 적어주세요.버블 정렬이해와 구현이 간단좋지 않은 성능O(n^2)선택 정렬이해과 우견이 간단좋지 않은 성능O(n^2)삽입 정렬이해과 우견이 간단좋지 않은 성능O(n^2)병합 정렬성능이 좋음이해와 구현이 어려움 O(nlogn)퀵 정렬성능이 좋음이해와 구현이 어려움Θ(nlogn) 메모리가 부족한 시스템에서 어떤 문제를 해결하는데 재귀로 쉽게 구현이 가능할 것 같습니다. 여러분이라면 메모이제이션과 타뷸레이션 중 어떤 걸 이용하실 건가요? 이유를 함께 적어주세요. 메모리가 부족한 시스템에서는 메모이제이션 대신 타뷸레이션을 사용메모이제이션은 중복 계산이 발생하여 성능 및 메모리 문제가 발생하지만 타뷸레이션은 중복된 값들을 저장하고 있어 중복 실행이 방지됨.

leeebug

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

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

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

호준

[인프런 워밍업 클럽 3기 - CS] 3주차 자료구조와 알고리즘 미션

1. 지금까지 배운 5개의 정렬 알고리즘의 장단점과 시간 복잡도를 적어주세요.버블정렬장점 : 구현이 간단하다.단점 : 성능이 좋지 않다.시간 복잡도 : O(n²)선택정렬장점 : 구현이 간단하다.단점 : 성능이 좋지 않다.시간 복잡도 : O(n²)삽입정렬장점 : 구현이 간단하다.단점 : 성능이 좋지 않다.시간 복잡도 : O(n²)병합정렬장점 : 성능이 좋다.단점 : 구현이 복잡하다.시간 복잡도 : O(nlogn)퀵정렬장점 : 성능이 좋다. 또한 단점 : 구현이 복잡하다. 시간 복잡도 : Θ(nlogn), O(n²) (대부분 좋은 피벗을 선택하고 최악의 경우가 발생할 경우가 극히 낮아서 병합정렬에 비해 더 적은 비교와 더 적은 메모리 공간을 차지하여 더 좋은 알고리즘으로 평가된다.) 2. 메모리가 부족한 시스템에서 어떤 문제를 해결하는데 재귀로 쉽게 구현이 가능할 것 같습니다. 여러분이라면 메모이제이션과 타뷸레이션 중 어떤 걸 이용하실 건가요? 이유를 함께 적어주세요.메모이제이션을 사용하여 재귀로 쉽게 구현은 가능할 수 있지만, 결국 메모리가 부족하여 시스템에서 문제가 발생할 우려가 있으므로 구현이 조금 복잡할 수 있더라도 조금 더 메모리를 효율적으로 사용할 수 있는 타뷸레이션을 이용할 것 같다.

알고리즘 · 자료구조워밍업클럽

호준

[인프런 워밍업 클럽 3기 - CS] 3주차 운영체제 미션

1. 메모리의 종류는 어떤 것들이 있나요? 각 메모리의 특징도 함께 적어주세요.레지스터 : 가장 빠른 기억장소로 CPU 내에 존재한다. 컴퓨터의 전원이 꺼지면 데이터가 사라지는 휘발성 메모리이다.캐시 : 메인 메모리에 있는 데이터를 레지스터로 옮기는 시간이 오래 걸리므로, 필요할 것 같은 데이터를 저장해둔다. 캐시는 성능 상의 이유로 여러 개를 둔다. 컴퓨터의 전원이 꺼지면 데이터가 사라지는 휘발성 메모리이다.메인 메모리 : 실제 운영체제와 다른 프로세스들이 올라가는 공간. 하드디스크나 SSD보다 속도는 빠르지만 비싸기 때문에 실행중인 프로그램만 올린다. 컴퓨터의 전원이 꺼지면 데이터가 사라지는 휘발성 메모리이다.보조저장장치(HDD, SSD) : 속도가 느리고 용량이 크고 가격이 저렴하다. 컴퓨터의 전원이 꺼져도 데이터가 사라지지 않는 비휘발성 메모리이다. 2. 사용자 프로세스가 메모리의 운영체제 영역에 침범하지 못하도록 만든 레지스터는 어떤 레지스터일까요?경계 레지스터. 메모리 관리자가 사용자 프로세스가 경계 레지스터의값을 벗어났는지 검사하고 만약 벗어났다면 종료시킨다.  3. 메모리 할당 방식에서 가변 분할 방식과 고정 분할 방식의 장단점은 뭔가요?가변 분할 방식장점 : 메모리의 연속된 공간에 할당되기 때문에 더 크게 할당되어 낭비되는 공간인 "내부 단편화"가 없다.단점 : 메모리의 빈 공간이 연속적으로 존재하지 않게 되는 "외부 단편화"가 발생한다.고정 분할 방식장점 : 구현이 간단하고 오버헤드가 적다.단점 : 작은 프로세스도 큰 영역에 할당되어 "내부 단편화"가 발생한다. 4. CPU 사용률을 올리기 위해 멀티프로그래밍을 올렸지만 스왑이 더 많이 이루어져 CPU 사용률이 0%에 가까워 지는 것을 뭐라고 할까요?스레싱  5. HDD나 SSD는 컴퓨터를 실행시키는데 꼭 필요한 걸까요?말 그대로 "컴퓨터를 실행"시키는 것이라면 운영체제 설치 없이도 전원이 들어오고 바이오스 진입까지는 가능하기 때문에 아니라고 볼 수 있으나, 현실적으로는 운영체제를 설치해야 하여 필수적이라고 보는 게 타당한 것 같다. 6. 파일을 삭제해도 포렌식으로 파일을 복구할 수 있는 이유가 무엇일까요?파일을 삭제할 때 파일시스템은 파일의 모든 정보를 지우는 것이 아니라 파일 테이블의 헤더만 삭제하고 사용했던 블록의 데이터는 그대로 남아있기 때문에 복구가 가능하다. 

시스템 · 운영체제워밍업클럽

[인프런 워밍업 클럽 3기 PM/PO] 3주차 발자국

강의 요약 지표 분석 우리의 제품이 어떤 식으로 변화하고 반응을 얻고 있는지 알기 위해서는 지표 상시 모니터링이 필요함. 우선 지표 중에서는 'Proxy Metric'이라는 것이 있음. 'Proxy Metric'이란 가정이 많이 들어간, 간접적인 지표를 의미함. 또한 강의에서는 모든 프로덕트에 적용할 수 있는 지표 설정 프레임 워크에 대해 설명하였음 : Acquisition, Activation, Engagement, Retention, Monetization 이 그 파이프라인임Acquisition : 고객 흭득 관련 지표(CAC, CLV, LTV) Activation : 신규 사용자들이 프로덕트의 핵심 가치를 형성하는 순간(?)을 지표로 나타내는 것 (Setup, Aha, Habit)Engagement: 과업 관련 지표 (DAU, WAU, MAU)Retention: 고객(사용자)이 제품을 계속해서 이용하는지에 대한 지표Monetization: 수익화 관련 지표 Event-Based Analytics - 이벤트 & 이벤트 상세 정보, User Properties가 무엇인지에 대해 배웠음.- 그리고 배운 정보들을 어떻게 분석에 활용하면 되는지에 대해 배웠음- 또한 이벤트 정보는 클라이언트 '또는' 서버에서 추적할 수 있다. 각각의 방식은 장단점이 있다. - 마지막으로 이벤트와 이벤트 상세 정보를 정의하고 관리하는 방법에 대해서 배웠음. 후기 데이터 분석을 공부한 적이 있었는데, 수업 내용 처럼 크게 지표를 보는 것 보다는 기술적인 면에 집중해서 보았다. 그래서 이번 강의는 프로덕트를 어떻게 만드는지에 대한 관점으로 지표를 보는 방법을 배웠기 때문에 의미가 깊었다. 과거에 내가 해결하지 못했던 문제들이 있었는데, 이 문제를 어떻게 해결할 수 있을지 다시 되새겨보고 설계해 볼 수 있는 의미있는 강의었다.  

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

Day 12섹션6. Spring & JPA 기반 테스트Persistence Layer 테스트 (1)Persistence Layer 테스트 (2)DataJpaTest는 JPA 관련 빈들만 등록해주기 때문에 SpringBootTest보다 빠르다@ActiveProfiles 어노테이션을 사용해 활성화 할 프로파일을 설정할 수 있다.리스트를 검증할 때는 size를 먼저 검증하고extracting + contains 조합을 많이 사용한다extracting 내부에는 검증할 필드들을 넣어주면되고contains는 다양한 api들이 있다.여기서 사용한 containsExactlyInAnyOrder()는 순서 상관없이 인자로 들어온 튜플들이 정확히 존재하는지extracting에 적은 필드의 순서대로 적어주면 된다.Day 13Business Layer 테스트 (1)Persistence Layer는 비즈니스 가공 로직이 포함되어서는 안 된다.Business Layer는 Persistence Layer와의 상호작용(Data를 읽고 쓰는 행위)을 통해 비즈니스 로직을 전개Business Layer 테스트 (2)SpringBootTest는 자동롤백이 안달려있고DataJpaTest는 자동롤백이 달려있음Day 14Business Layer 테스트 (3)Day 15Presentation Layer 테스트 (1)모킹: 가짜 객체로 대신하여 정상 동작할거야 라는 것을 가정하고프레젠테이션 레이어즉, 테스트하고자 하는 레이어에만 집중해서 테스트하겠다readOnly = true : 읽기전용 트랜잭션CRD 작업이 동작 X / only ReadJPA : CUD 스냅샷 저장, 변경감지 등을 안해도 되기 때문에 성능 향상CQRS - Command(CUD) / Query(R)미션Day 11사용자의 입력값은 무조건 불신을 깔고 들어가야한다는 강사님의 말씀이 떠올랐다.그래서 InputHandler를 테스트하고자 했다.StudyCafeIOHandler를 테스트해야하나 고민했지만, 통합해주는 역할일뿐입력에 대한 최종 책임은 InputHandler에 있다고 판단했다.기존 InputHandler에는private static final Scanner SCANNER = new Scanner(System.in);로 스캐너가 생성되어있어서 테스트할 때 nextLine() 예외가 발생했다.private final Scanner scanner; public InputHandler(Scanner scanner) { this.scanner = scanner; }사용자 입력같은 테스트하기 어려운 영역을 분리하자는 강사님의 말씀이 떠올라 외부에서 주입받도록InputHandler를 변경해주었다.또 해피 케이스말고 예외 케이스를 생각해 1~3 이외에 다른 입력시 들어왔을 때 예외가 잘 발생하는지 확인했다.또 사물함을 사용할 수 있는 패스권인지 확인하는 메서드를 검증하기 위해StudyCafePassType에 대한 테스트를 진행했다.확실히 enum 타입으로 객체로 만드니 관련 로직을 위한 공간이 생겨 테스트가 용이하구나를 느꼈다.그리고 가장 중요한 금액관련 테스트를 진행하고자했다.금액 관련 테스트를 StudyCafePassOrder에서 전부 다 진행할까 했지만,하나의 테스트는 하나의 책임만 가져야한다고 생각해서,StudyCafeSeatPass, StudyCafeLockerPass에서 각각 진행했다.테스트를 진행하면서 궁금한 점은StudyCafeLockerPass lockerPass = StudyCafeLockerPass.of(StudyCafePassType.FIXED, 4, 10000);처럼 of 메서드에서 csv 파일에 맞게 직접 입력해주는게 맞을지아니면 LockerPassFileReader에서 객체를 찾아내서 테스트 하는게 맞을지 궁금하다.그리고 DisplayName 짓는 것이 생각보다 되게 까다로웠다.테스트는 쉽지않다. 하지만 올바르지 못한 테스트는 오히려 혼란을 일으킬뿐이라는 것은 알게되었다.제대로 된 테스트를 작성하도록 노력하자.모킹 테스트를 왜 해야는지는 아직 명확하게 와닿진 않는다.알 때 까지 복습하자.

백엔드워밍업클럽테스트발자국박우빈

szun

워밍업 클럽 스터디 3기 (PM) 3주차 발자국

강의명: 시작하는 PM/PO들에게 알려주고 싶은, 프로덕트의 모든 것코치: 김민우링크: https://www.inflearn.com/course/%EC%8B%9C%EC%9E%91%ED%95%98%EB%8A%94-pmpo-%EB%AA%A8%EB%93%A0%EA%B2%83/dashboard3주차 강의 포인트 지표(Metric)란?우리 사업, 제품의 현황과 성과를 측정해서 정량화 한 것Acquisition, Activation, Engagement, Retention, Monetization 등의 지표를 상시로 모니터링 하면우리 제품의 현황이 어떠한지, 어떤 추이로 변화하고 있는지 알 수 있고,지표를 토대로 의사결정을 하고 과업을 수행할 수 있음.Proxy 지표가정이 많이 들어간, 간접적인 지표.즉, 어떠한 값을 측정하고 싶은데 해당 값이 정량적이지 않아 직접 측정이 어려울 때, 가정을 포함하여 설정한 대체 지표.Proxy 지표는 완벽하지 않을 수 있지만, 설정했다는 것 자체만으로 의미가 있음. 모든 프로덕트에 적용할 수 있는 대표적인 지표 5가지Acquisition질문 1. 우리는 충분히 많은 신규 유저/고객을 획득하고 있는가?질문 2. 신규 유저/고객을 비용 효율적으로 획득하고 있는가?CAC (고객 획득 비용)Customer Lifetime Value (고객 생애 가치)Payback Period (비용 회수 기간)Activation신규 획득한 사용자들이 프로덕트의 핵심 가치를 경험하는 습관을 형성하는 것Activation은 Retention에 영향을 주는 핵심 요소 중 하나임.Activation 3단계Step Moment사용자가 제품의 핵심 가치를 경험하기 위한 '준비'를 마친 순간Aha Moment사용자가 '처음으로' 제품의 핵심 가치를 경험한 순간Habit Moment사용자가 제품의 핵심 가치를 경험하는 '습관'을 형성한 순간Engagement사용자들이 프로덕트에 관심을 갖고, 이용하고, 관계를 맺는 것얼마나 많은 유저들이 이용하는가 (Breadth)제품을 얼마나 깊이 있게 이용하는가 (Depth)얼마나 자주 이용하는가 (Frequency)성공적으로 과업을 완수하는가 (Efficiency)Retention고객(사용자)들이 제품을 계속해서 이용하는 것Retention Rate특정 기간 동안 고객(사용자)들이 유지되는 비율리텐션율을 이야기할 때는 항상 기간과 분자, 분모를 정의해야 함.Retention 측정/관찰 방식Cohort Retention (코호트 리텐션)특정 cohort user들이 시간이 경과함에 따라 유지되는 비율Retention CurveDay N RetentionN일째 유지된 사용자의 비율Bracket(Bounded) Retention어떤 기간 내에 유지된 사용자 비율Unbounded(On and After) Retention Monetization대표적인 Monetization의 지표들매출기간별 매출 성장률Paying User 수인당 매출(객단가) 지표Net Revenue Retention Metric Hierarchy, Input/Output 지표Metric Hierarchy : 지표의 위계 구조하나의 output 지표에는 다양한 input 지표들이 작용한 결과.수식으로 명확하게 표현되지 않는 지표 간의 관계도 존재.이는 프로덕트에 대해 많은 고민과 리서치를 하고 여러 케이스에 대한 실험으로 알아내는 수 밖에 없음. Product Analytics의 주요 개념들데이터는 투자가 필요한 자원이다.어떤 데이터를 축적할 것인지, 어떤 형태로 어디에 데이터를 기록할 것인지 정의가 필요.Product Analytic의 주요 개념들Event-Based Analytics이벤트를 기반으로 하는 데이터 분석Event유저와 제품 사이에 일어나는 상호작용Event Property이벤트에 수반되는 상세 정보User Property유저에 대한 정보Date TypeClient-side/Server-side Tracking어디에서 데이터를 트래킹할 것인지에 대한 기준 Event Taxonomy Event Taxonomy 설계란?어떤 event, 어떤 property를 트래킹할 것인지 구체적으로 정의하는 작업 두 가지 접근 방식Top-Down 접근데이터를 활용하는 '목적'에서부터 정의하는 것Bottom-Up 접근'우리 제품의 주요 이벤트는 무엇인가?'에서 시작 Naming Convention다음 요소들을 고려해야 함.일관성이벤트 이름을 설정할 때 일관성을 지켜야 함명확성부여한 이름이 무엇을 뜻하는지 명확하게 설정해야 함이벤트를 얼마나 잘게 쪼갤 것인가?각 행동이 '구분되는 서로 다른 종류의 행동'인지, 아니면 '본질적으로 같은 행동'인지 판단해서 설정하기  Event Taxonomy 문서를 만들 시 꼭 포함시켜야 하는 정보Naming Convention이벤트 정보이번트 프로퍼티 정보유저 프로퍼티 정보3주차 회고  이번 주차 강의는 최근 데이터 분석을 공부하고자 마음을 먹은 나에게 많은 도움을 주는 강의였다. 데이터 분석을 공부하고 있지만 어떤 지표를 기준으로 데이터를 수집하여 분석을 해야 하는지 감이 잡히지 않는 상황에서 이번 내용들은 길잡이가 되어 주었다. 하지만 Proxy 지표와 같은 가정을 필요로 하는 데이터들을 다루는 것에 대해서는 여전히 걱정이 많다. 객관적이고 명확한 정보에서 편안함을 느끼는 나의 경우, 반대의 성향을 지닌 정보들에서는 불안함이 수반되는 것 같은 느낌을 받는다. PM에 대해 공부할수록 PM이라는 직무가 굉장히 추상적인 것 같다는 인상을 계속해서 받게 된다. 이걸 해결하기 위한 방법은 아무래도 경험뿐이라는 생각이 드는데, 이런 모호함을 떨쳐내는 것이 나의 숙제이지 않을까 싶다.

기획 · PM· PO

[워밍업 클럽 3기 BE code] 2주차

테스트가 필요한 이유사람이 애플리케이션을 실행하면서 잘 작동되는지 확인하는 작업은 놓칠 수도 있고, 시간이 오래 걸려 비효율적이다.테스트 코드 작성의 이점빠른 피드백: 코드 수정 후 바로 검증 가능자동화: 반복적인 수동 테스트가 불필요안정성: 코드 변경이 발생해도 기존 기능이 정상 동작하는지 보장디버깅 용이: 문제 발생 시 원인 파악이 쉬움단위 테스트작은 코드 단위를 독립적으로 검증하는 테스트 → 검증 속도가 빠르고 안정적이다.관련 라이브러리JUnit5: 단위 테스트를 위한 테스트 프레임워크AssertJ: 테스트 코드 작성을 원활하게 돕는 라이브러리수동 테스트 vs 자동화된 테스트 비교수동 테스트사람이 직접 성공/실패를 판단실수 가능성 높음반복이 어려움시간이 오래 걸림자동화된 테스트시스템이 자동으로 판단실수 가능성 낮음반복이 쉬움소요 시간이 짧음 자동화된 테스트를 활용하면 수동 테스트의 한계를 극복할 수 있다.자동화 테스트를 작성하면 테스트 결과만 확인하면 되므로 빠르고 정확하다.테스트 케이스 세분화하기해피 케이스(정상 동작)*만 작성하지 않고 예외 케이스(예상하지 못한 상황)도 함께 테스트해야 한다.TDD (Test Driven Development)프로덕션 코드보다 테스트 코드를 먼저 작성하여, 테스트가 구현 과정을 주도하도록 하는 개발 방법론빠른 피드백을 통해 코드 품질을 높이고, 유연한 설계를 가능하게 함TDD의 장점내 코드의 피드백을 빠르게 받을 수 있음리팩토링이 쉬워짐 → 테스트가 있으므로 기존 기능이 정상 동작하는지 보장됨테스트 가능한 코드 설계 → 단일 책임 원칙(SRP)에 맞게 코드 구조를 고민하게 됨디버깅 시간 단축 → 기능 개발 중 발생하는 문제를 사전에 방지할 수 있음기능 구현 방식 비교방식 장점 단점선 기능 구현, 후 테스트 작성장점 : 구현이 직관적이고 빠르게 가능단점 : 테스트 누락 가능성, 특정 케이스만 검증, 잘못된 구현 발견 지연  선 테스트 작성, 후 기능 구현 (TDD)장점 : 테스트가 어려운 영역을 미리 발견하여 설계를 개선할 수 있음단점 : 초기 개발 속도가 다소 느릴 수 있음TDD의 핵심 원칙: Red → Green → Refactor1⃣ Red (실패하는 테스트 작성)테스트 코드를 먼저 작성하고 실행 → 아직 기능이 없으므로 테스트가 실패해야 한다.2⃣ Green (기능 구현하여 테스트 통과)테스트가 통과하도록 최소한의 기능을 구현한다.3⃣ Refactor (리팩토링)중복 제거, 코드 개선을 통해 더 나은 구조로 변경한다.리팩토링 후에도 테스트가 통과하는지 확인한다.테스트는 [문서]다테스트 코드는 해당 기능의 동작을 설명하는 문서 역할을 한다.@DisplayName을 활용하여 테스트 목적을 명확히 하자.@DisplayName("회원 가입 시 이메일이 중복되면 예외가 발생한다") @Test void 회원가입_이메일중복_예외발생() { // Given // When // Then }생각 정리테스트의 중요성에 대해서는 인지하고 있었지만 테스트 코드 작성이 항상 귀찮고 어렵게 느껴졌었다. 하지만 테스트 코드를 작성하고 결과를 확인하는 과정 자체가 더 나은 프로덕션 코드를 만드는 과정이 될 수 있다는 것을 깨달았다.특히 TDD의 경우에는 기능이 없는데 테스트 코드를 먼저 작성하는게 이해가 안 됐었는데 테스트를 실패하는 최소한의 기능을 먼저 만들고 점진적으로 더 나은 코드 작성 및 설계를 할 수 있도록 유도하는 과정이라는 것을 알게 되었다. 강의https://www.inflearn.com/course/practical-testing-%EC%8B%A4%EC%9A%A9%EC%A0%81%EC%9D%B8-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B0%80%EC%9D%B4%EB%93%9C/dashboard

jinwoo2511

인프런 워밍업 클럽 스터디 3기 - CS 전공지식 <1주차 발자국>

'그림으로 쉽게 배우는 운영체제'[섹션 01]운영체제란??컴퓨터의 하드웨어적인 요소들과 소프트웨어적인 요소들을 효율적으로 운영하여 관리함으로써 사용자가 시스템을 이용하는데에 편리함을 제공하는 시스템 소프트웨어를 말합니다.운영체제 종류 및 특징 유닉스/리눅스대화식 운영체제사용자가 명령을 입력하면 시스템이 명령을 수행다중작업한번에 하나 이상의 작업을 수행할 수 있음다중 사용자여러 사람이 동시에 각각 작업을 수행할 수 있음이식성90%이상 C언어로 구성, 시스템이 모듈화 되어 있어 다른 하드웨어 기종에 쉽게 이식 가능계층적 파일 시스템 제공계층적 트리 구조를 가짐유닉스와 리눅스의 차이점리눅스는 대부분 무료이지만 유닉스는 대부분 유료리눅스는 개발자 및 일반 사용자가 사용, 유닉스는 대형 시스템 관리자가 사용리눅스는 오픈 소스 개발, 유닉스는 사업자에 의해 배포 비용 수반리눅스는 BASH 쉘 사용, 유닉스는 커맨드 기반 윈도우그래픽 사용자 인터페이스(GUI) 제공키보드없이 마우스로 아이콘이나 메뉴를 선택하여 작업 수행 가능선점형 멀티 태스킹 방식 제공동시에 여러개의 프로그램을 실행하며, 운영체제가 각 작업의 CPU 이용시간 제어자동 감지 기능 (Plug & Play)하필요한 시스템 환경을 운영체제가 자동으로 구성OLE(Object Linking and Embedding) 사용개체를 현재 작성 중인 문서에 자유롭게 연결 또는 삽입 가능 IOSiOS는 사용자 경험과 인터페이스에 매우 중점을 둠하드웨어 및 소프트웨어 최적화강력한 보안 및 프라이버시 기능OS는 앱스토어를 통해 다양한 애플리케이션을 제공하며, 개발자들에게 큰 수익 창출의 기회를 제공  운영체제 특징사용자 편리성한정된 시스템 자원을 효과적으로 사용할 수 있도록 관리 및 운영인터페이스 기능컴퓨터 시스템과 사용자를 연결스케줄링자원의 현재 상태를 파악, 자원 분배를 위한 스케줄링을 담당자원관리프로세스 관리메모리 관리하드웨어 관리파일 시스템 관리입출력 프로그램 관리제어기능입출력 장치와 사용자 프로그램을 제어 운영체제 역사 1940년도애니악 개발애니악 특징운영체제가 없는 최초의 컴퓨터응용 프로그램이 시스템 자원을 제어스위치와 배선을 연결해서 프로그래밍문제 발생 시 종이에 작업 후 테스트 마침 => 기간이 많이 소모되는 단점30톤으로 수많은 진공관으로 구성되어 있기 때문에, 하드웨어 비용이 굉장히 비쌈 1950년대 초진공관과 전선으로 만들어진 논리 회로를 아주 작은 크기로 만든 직접회로 개발CPU와 메모리가 존재하지만 키보드와 모니토는 존재하지 않음펀치 카드에 프로그래머가 카드의 구멍을 뚫어 프로그래밍컴퓨터가 카드를 읽고 계산 후 결과를 라인 프린터로 출력기존 스위치 배선 작업보다 편해짐 1950년도 중후반이전 작업은 오퍼레이터의 오버헤드가 너무 큼싱글 스트림 배치 시스템 개발여러개의 프로그램을 순서대로 실행해서 결과를 한번에 확인할 수 있는 시스템 I/O Device 컨트롤러를 만들어 입출력 중에도 CPU가 계산할 수 있도록 만듦입출력 작업이 끝나면 CPU에게 인터럽트 신호를 주고 인터럽트를 받은 CPU는 다시 처리를 하는 식으로 발전입출력에도 CPU를 기다려야 하는 작업이 필요하다는 단점이 있음   1960년도 이후메모리 침범 이슈 발생다중 프로그램으로 인한 메모리 주소 이슈 발생하드웨어적으로 베이스 레지스터라는 것을 추가해서 프로그램의 시작 주소를 저장하고 모든 프로그램은 영번지에서 실행한다고 가정CPU의 사용률과 효율성을 중요한 문제로 인식 1970년도 이후개인용 컴퓨터의 시대가 시작저렴해진 컴퓨터의 개인 소유가 쉬워짐애플의 매킨토시와 마이크로소프트의 MS-DOS가 많이 사용매킨토시는 GUI를 도입해서 굉장한 인기CPU 사용률과 비용 절감을 위한 노력으로 오늘날의 운영체제가 탄생  운영체제의 구조커널운영체제의 핵심 기능들이 모여있는 컴퓨터 프로그램커널의 기능프로세스 관리기억 장치 관리주변장치 관리파일 관리사용자로부터 자신을 보호하기 위한 시스템 콜이라는 인터페이스 보유시스템 콜을 이용하면 커널에서 제공하는 write() 함수를 쓰게 되는데 하드디스크의 빈 공간에 저장 인터페이스GUI그래픽 사용자 인터페이스의 약자로 말 그대로 그래픽으로 된 인터페이스윈도우나 맥OS와 같이 그래픽으로 커널과 상호작용하기 때문에 일반 사용자도 사용하기가 쉬움 CLI명령줄 인터페이스의 약자요즘은 리눅스는 GUI 환경을 제공하지만 많은 사용자들이 리눅스의 CLI를 선호 컴퓨터 하드웨어와 구조현재 컴퓨터는 프로그램 내장 방식의 폰 노이만 구조폰 노이만은 CPU와 메모리 사이를 버로 연결함버스는 데이터를 전달하는 통로프로그램 내장 방식은 프로그램을 메모리에 올려서 실행시키는 방식메인보드다른 하드웨어를 연결하는 장치다양한 단자가 있음ex) 출력단자, 그래픽 카드 단자, USB 단자, 사운드 단자 등등장치간의 데이터를 전송하는건 메인보드의 버스가 담당CPU1. 산술논리 연산 장치데이터 연산 담당2. 제어 장치모든 장치들의 동작 지시 및 제어3. 레지스터CPU 내에서 계산을 위해 임시적으로 보관하는 장소 RAM랜덤으로 데이터를 읽어도 저장된 위치와 상관 없이 읽는 속도가 동일전력이 끊기면 데이터를 잃어버리기 때문에 메인 메모리로 사용 ROM전력이 끊겨도 데이터 영구 보관데이터를 한번 쓰면 수정 불가  컴퓨터 부팅 과정ROM에 저장된 BIOS 가 실행됨바이오스 - BIOS (Basic Input Output System)메모리와 CPU 레지스터를 초기화 시킨다.디스크로부터 부트 로더를 불러 온다바이오스는 롬에 저장되어 있기 때문에 램에 저장된 정보와는 달리, 컴퓨터를 끄더라도 그 내용이 지워지지 않음  주요 하드웨어에 이상이 없는지 확인 후 부팅 이상이 있을 경우 경고음을 내면서 부팅이되지 않음운영체제가 2개 설치되어 있을 경우, 운영체제를 선택하는 화면이 활성화됨 부팅 후 실행되는 모든 프로그램은 램에 올라와서 운영체제에 의해 관리  폴딩주기적으로 CPU가 입출력 관리자에게 주기적이고 지속적으로 입출력이 왔는지 확인폴링 방식의 단점은 주기적으로 CPU가 확인해줘야 하니 성능이 좋지 않다는 단점이 존재    인터럽트폴링 방식의 단점을 해결한 방식입출력이 완료되었을 때 CPU에게 신호를 주어 인터럽트 서비스 루틴을 실행하는 방식서비스루틴 이란?특정 인터럽트가 들어오면 그 인터럽트를 처리하는 함수  [섹션 02]프로그램이란?하드디스크 등과 같은 저장장치에 저장된 명령문의 집합체 프로세스란?하드디스크에 저장된 프로그램이 메모리에 올라갔을 때 실행중인 프로그램메모리도 사용하고 운영체제의 CPU 스케줄링 알고리즘에 따라서 CPU도 사용하고 필요에 따라 입력과 출력을 하기 때문에 능동적인 존재코드 영역, 데이터 영역, 스택 영역, 킵 영역을 갖고 있음코드 영역자신을 실행하는 코드가 저장되어 있고 데이터 영역은 전역 변수와 스태틱 변수가 저장스택 영역지역 변수와 함수 호출을 했을 때 필요한 정보들이 저장힙 영역프로그래머가 동적으로 메모리를 할당ex) C언어에서 malloc, free 함수를 호출하면 힙 영역에 자원을 할당, 해제 컴파일 과정고급 언어(예: C, C++)로 작성된 소스 코드를 기계어(바이너리 코드)로 변환하는 과정4단계(전처리 → 컴파일 → 어셈블 → 링크)로 진행 1. 전처리 (Preprocessing)소스 코드에서 전처리 지시문(#으로 시작하는 문장)을 처리하는 단계생성 파일 : .i 로 끝남 2. 컴파일 (Compilation)전처리된 코드를 어셈블리 코드(assembly language)로 변환하는 단계생성 파일 : .s 로 끝남주요 작업문법 검사 및 오류 확인최적화 수행어셈블리 코드 생성 3.어셈블 (Assembling)어셈블리 코드를 기계어(오브젝트 파일, .o 또는 .obj)로 변환하는 단계입니다.생성 파일 : .o 로 끝남주요 작업명령어를 바이너리 코드로 변환오브젝트 파일 생성4. 링크 (Linking)여러 개의 오브젝트 파일과 라이브러리를 결합하여 실행 파일(Executable, .exe 또는 a.out)을 생성하는 단계생성 파일 : .exe, .out 로 끝남  멀티 프로그래밍과 멀티 프로세싱 멀티 프로그래밍하나의 CPU가 여러 프로세스를 번갈아 실행특징CPU 이용률 극대화비선점형 스케줄링에 많이 사용병렬 처리가 아닌 시분할 방식 멀티 프로세싱여러 개의 CPU가 동시에 여러 프로세스를 처리특징병렬 처리 가능선점형 스케줄링 적용다중 코어 시스템에서 사용PCB (Process Control Block)운영체제가 각 프로세스를 관리하기 위해 유지하는 데이터 구조체프로세스의 상태, 메모리 정보, CPU 레지스터 정보 등을 저장구성 요소PID (Process ID)프로세스 상태프로그램 카운터CPU 레지스터메모리 관리 정보입출력 상태 정보우선순위 프로그램 카운터가 필요한 이유는 어떤 프로세스가 실행되다가 다른 프로세스에게 CPU를 뺏기고 다시 실행될 때 원래 실행하던 명령어가 실행되어야 하기 때문에 프로그램 카운터가 꼭 있어야 함 프로세스 상태1. 생성새로운 프로세스 생성       2. 준비CPU 할당을 기다리는 상태 3.실행CPU가 프로세스를 실행 중4.대기/O 작업 등으로 대기 중5. 완료실행이 끝난 상태컨텍스트 스위칭 (Context Switching)CPU가 현재 실행 중인 프로세스를 중단하고, 다른 프로세스로 전환하는 작업PCB에 현재 프로세스의 상태 정보를 저장하고, 다음 프로세스의 상태 정보를 불러옴절차1. 현재 프로세스의 상태 저장 (PCB)2. 새로운 프로세스의 PCB 불러오기3. CPU 레지스터 및 메모리 정보 복원4. 새로운 프로세스 실행 프로세스 생성과 종료프로세스 생성 과정1. 부모 프로세스가 fork() 또는 CreateProcess() 호출2. 운영체제가 PCB 생성3. 메모리 할당 및 초기화4. Ready 상태로 전환프로세스 종료 과정1. 프로세스 작업 완료 또는 오류 발생2. 운영체제가 자원 회수3. PCB 제거4. Terminated 상태로 전환 쓰레드 (Thread)프로세스 내에서 실행 흐름 단위독립적인 스택과 레지스터를 가짐같은 프로세스의 쓰레드는 코드, 데이터, 힙 영역 공유쓰레드 종류커널 레벨 쓰레드: 운영체제에서 관리유저 레벨 쓰레드: 사용자 공간에서 관리  [섹션 03] CPU 스케줄링 이란?CPU 스케줄링은 운영체제가 CPU를 여러 프로세스에게 효율적으로 할당하는 기법이는 시스템 성능을 최적화하고 공정성을 보장하며, 응답 시간과 처리율을 개선하는 역할   2. 스케줄링 목표CPU 이용률CPU를 최대한 활용하도록 함처리량단위 시간당 처리할 수 있는 프로세스 수를 최대화대기 시간 최소화프로세스가 CPU를 기다리는 시간을 줄임응답 시간 최소화사용자의 요청에 빠르게 반응공정성모든 프로세스가 공정하게 CPU를 할당받도록 보장 다중 큐 (Multi-Queue)다중 큐 스케줄링은 프로세스를 여러 개의 큐로 분류하고, 각 큐에 다른 스케줄링 정책을 적용하는 방식ex) 시스템 프로세스는 높은 우선순위를 가진 큐에서 실행되고, 사용자 프로세스는 낮은 우선순위 큐에서 실행  FIFO (First In First Out)먼저 들어온 프로세스가 먼저 실행됨 (큐의 구조와 유사)장점: 구현이 간단함단점: 짧은 작업보다 긴 작업이 먼저 실행되면 Convoy Effect 발생 (짧은 작업이 뒤에서 오래 대기하는 문제) SJF (Shortest Job First)실행 시간이 가장 짧은 프로세스를 먼저 실행장점: 평균 대기 시간을 최소화단점: 실행 시간이 긴 프로세스가 계속 뒤로 밀려 기아 현상(Starvation) 발생 가능 타임 슬라이스 (Time Slice)란?타임 슬라이스는 CPU가 각 프로세스에 할당하는 최대 실행 시간을 의미타임 퀀텀(Time Quantum) 이라고도 부르며, 보통 밀리초(ms) 단위로 설정한 프로세스가 타임 슬라이스만큼 실행된 후, 문맥 전환(Context Switch) 을 통해 다른 프로세스로 넘어감 타임 슬라이스의 특징짧은 타임 슬라이스빠른 응답성을 제공하지만 문맥 전환 비용이 증가긴 타임 슬라이스문맥 전환 비용이 줄지만 응답 시간이 길어짐적절한 타임 슬라이스 값을 설정하는 것이 중요일반적으로 타임 슬라이스는 문맥 전환 비용보다 충분히 커야 효율적 타임 슬라이스가 적용되는 스케줄링 알고리즘1. RR (Round Robin)2. MLFQ (Multi-Level Feedback Queue) RR (Round Robin)일정한 시간동안 프로세스를 실행한 후, 다음 프로세스로 전환 장점: 공정성이 보장됨 (모든 프로세스가 CPU를 일정 시간 동안 사용할 기회 제공)단점: 시간 할당량이 너무 크면 FIFO와 유사해지고, 너무 작으면 문맥 전환(Context Switch) 비용 증가 MLFQ (Multi-Level Feedback Queue)여러 개의 큐를 사용하여, 우선순위를 동적으로 조정하는 방식 처음에는 높은 우선순위 큐에서 실행CPU를 많이 사용하면 낮은 우선순위 큐로 이동I/O 중심 프로세스는 높은 우선순위를 유지 장점: CPU 및 I/O 중심 프로세스를 균형 있게 처리할 수 있음단점: 정책을 잘못 설계하면 특정 프로세스가 기아 상태에 빠질 가능성이 있음  '그림으로 쉽게 배우는 자료구조와 알고리즘(기본편)' 자료구조와 알고리즘이란?자료구조(Data Structure): 데이터를 효율적으로 저장하고 관리하는 방법알고리즘(Algorithm): 문제를 해결하는 절차나 방법 시간 복잡도(Time Complexity)란?알고리즘이 실행되는 연산 횟수를 입력 크기(n)에 따라 분석한 것주로 빅오 표기법(O-notation)으로 표현배열(Array) 개념같은 자료형의 연속된 메모리 공간을 차지하는 데이터 구조빠른 접근(O(1)) 가능, 하지만 삽입/삭제(O(N))이 느림 배열 구현#include <iostream> using namespace std; int main() { int arr[5] = {1, 2, 3, 4, 5}; cout << "배열 요소 출력: "; for(int i = 0; i < 5; i++) { cout << arr[i] << " "; } return 0; } 연결 리스트(Linked List) 개념노드(Node)들이 포인터로 연결된 구조삽입/삭제가 빠름(O(1)), 접근 속도가 느림(O(N))단일 연결 리스트(Singly Linked List), 이중 연결 리스트(Doubly Linked List) 등이 있음연결리스트 구현#include <iostream> using namespace std; struct Node { int data; Node* next; Node(int val) : data(val), next(nullptr) {} }; class LinkedList { public: Node* head; LinkedList() : head(nullptr) {} void insert(int data) { Node* newNode = new Node(data); newNode->next = head; head = newNode; } void remove(int data) { Node* temp = head; Node* prev = nullptr; while (temp && temp->data != data) { prev = temp; temp = temp->next; } if (!temp) return; // 삭제할 노드 없음 if (prev) prev->next = temp->next; else head = temp->next; // 첫 번째 노드 삭제 시 delete temp; } void display() { Node* temp = head; while (temp) { cout << temp->data << " -> "; temp = temp->next; } cout << "NULL\n"; } }; int main() { LinkedList list; list.insert(10); list.insert(20); list.insert(30); list.display(); list.remove(20); list.display(); return 0; } 스택(Stack) 개념LIFO(Last In First Out) 구조push(삽입), pop(제거), top(최상단 요소 확인)스택 구현 (C++)#include <iostream> #include <stack> using namespace std; int main() { stack<int> s; s.push(10); s.push(20); s.push(30); cout << "Top: " << s.top() << endl; s.pop(); cout << "Top after pop: " << s.top() << endl; return 0; }  큐(Queue) 개념FIFO(First In First Out) 구조push(삽입), pop(제거), front(첫 요소), back(마지막 요소)큐 구현 (C++)#include <iostream> using namespace std; class Queue { private: struct Node { int data; Node* next; Node(int val) : data(val), next(nullptr) {} }; Node *frontNode, *rearNode; public: Queue() : frontNode(nullptr), rearNode(nullptr) {} void enqueue(int data) { Node* newNode = new Node(data); if (!rearNode) { frontNode = rearNode = newNode; return; } rearNode->next = newNode; rearNode = newNode; } void dequeue() { if (!frontNode) return; Node* temp = frontNode; frontNode = frontNode->next; if (!frontNode) rearNode = nullptr; delete temp; } int front() { return (frontNode) ? frontNode->data : -1; } bool isEmpty() { return frontNode == nullptr; } }; int main() { Queue q; q.enqueue(10); q.enqueue(20); q.enqueue(30); cout << "Front: " << q.front() << endl; q.dequeue(); cout << "Front after dequeue: " << q.front() << endl; return 0; }  덱(Deque) 개념양방향 삽입/삭제 가능한 자료구조push_front(), push_back(), pop_front(), pop_back() 제공덱 구현 (C++)#include <iostream> using namespace std; class Deque { private: struct Node { int data; Node* next; Node* prev; Node(int val) : data(val), next(nullptr), prev(nullptr) {} }; Node *frontNode, *rearNode; public: Deque() : frontNode(nullptr), rearNode(nullptr) {} void push_front(int data) { Node* newNode = new Node(data); if (!frontNode) { frontNode = rearNode = newNode; } else { newNode->next = frontNode; frontNode->prev = newNode; frontNode = newNode; } } void push_back(int data) { Node* newNode = new Node(data); if (!rearNode) { frontNode = rearNode = newNode; } else { newNode->prev = rearNode; rearNode->next = newNode; rearNode = newNode; } } void pop_front() { if (!frontNode) return; Node* temp = frontNode; frontNode = frontNode->next; if (frontNode) frontNode->prev = nullptr; else rearNode = nullptr; delete temp; } void pop_back() { if (!rearNode) return; Node* temp = rearNode; rearNode = rearNode->prev; if (rearNode) rearNode->next = nullptr; else frontNode = nullptr; delete temp; } int front() { return (frontNode) ? frontNode->data : -1; } int back() { return (rearNode) ? rearNode->data : -1; } }; int main() { Deque d; d.push_back(10); d.push_front(20); cout << "Front: " << d.front() << ", Back: " << d.back() << endl; d.pop_front(); cout << "Front after pop: " << d.front() << endl; return 0; }  해시 테이블(Hash Table) 개념키-값(Key-Value) 쌍으로 데이터를 저장하는 자료구조탐색 속도 O(1), 충돌 해결 방법 필요 (체이닝, 개방 주소법 등)해시 테이블 구현 (C++)#include <iostream> #include <vector> using namespace std; class HashTable { private: static const int SIZE = 10; vector<pair<int, int>> table[SIZE]; int hashFunction(int key) { return key % SIZE; } public: void insert(int key, int value) { int hashIndex = hashFunction(key); table[hashIndex].push_back({key, value}); } int get(int key) { int hashIndex = hashFunction(key); for (auto &p : table[hashIndex]) { if (p.first == key) return p.second; } return -1; } }; int main() { HashTable ht; ht.insert(1, 100); ht.insert(11, 200); cout << "Key 1: " << ht.get(1) << endl; cout << "Key 11: " << ht.get(11) << endl; return 0; }  셋(Set) 개념중복을 허용하지 않는 집합 자료구조삽입/삭제 O(log N) (Balanced BST 사용) 셋 구현 (C++)#include <iostream> using namespace std; class Set { private: struct Node { int data; Node* next; Node(int val) : data(val), next(nullptr) {} }; Node* head; public: Set() : head(nullptr) {} void insert(int data) { if (contains(data)) return; Node* newNode = new Node(data); newNode->next = head; head = newNode; } bool contains(int data) { Node* temp = head; while (temp) { if (temp->data == data) return true; temp = temp->next; } return false; } }; int main() { Set s; s.insert(10); s.insert(20); s.insert(10); return 0; }  

수뼈

인프런 워밍업 클럽 스터디 3기 - CS 전공지식(운영체제) <둘째 주 미션>

1. FIFO 스케줄링의 장단점이 뭔가요?장점은 쉬운 구현과 실행 결과 예측의 용이성이고, 단점은 호위 효과와 사용성 저하입니다. FCFS(First Comes, First Served) Algorithm이라고도 하는 FIFO Scheduling은 이름대로 모든 프로세스를 단일 Ready Queue에 넣고 순차 실행합니다. Time Slice, Timeout Interrupt 등을 통한 Context Switching도 구현되지 않은 때의 비선점 알고리즘인 만큼 구현이 쉽고 실행 결과 예측이 용이합니다. 그러나 Burst Time이 긴 게 먼저 오느냐, 짧은 게 먼저 오느냐에 따라 평균 대기 시간 차이가 크게 벌어지는 Convoy Effect가 발생하며, 멀티 프로세싱이 불가능하므로 사용성이 크게 저하됩니다.2. SJF를 사용하기 어러운 이유가 뭔가요?각 프로세스의 Burst Time 예측 불가능성, Burst Time이 긴 프로세스의 Starvation 때문입니다. SJF(Shortest Job First) Scheduling은 Burst Time이 짧은 프로세스를 우선 실행하는 알고리즘입니다. 따라서 각 프로세스의 Burst Time을 정확히 예측하고, 그걸 바탕으로 Ready Queue에 줄세워야 하는데 이것은 외부 환경 등에 의해 큰 영향을 받는 Burst Time 특성상 현실적으로 줄세우기가 불가능하단 것이 첫 번째 이유입니다. 두 번째로, 어찌어찌 모든 프로세스를 잘 줄세웠다고 해도 Burst Time이 아주 긴 프로세스는 최악의 경우, 컴퓨터가 종료될 때까지 단 한 번도 실행되지 못할 수도 있습니다.3. RR 스케줄링에서 타임 슬라이스가 아주 작으면 어떤 문제가 발생할까요?매우 큰 Context Switching Overhead가 발생해 시스템 성능을 크게 저하됩니다. 컨텍스트 스위칭 작업에는 CPU 레지스터나 프로그램 카운터 같은 상태 정보 저장 및 복원, 메모리 캐시 무효화 등의 오버헤드 요소가 산재해 있습니다. 만약 타임 슬라이스가 극단적으로 짧다면 CPU가 연산보다 컨텍스트 스위칭에 더 많은 리소스와 시간을 소모하게 되어 전체 시스템 처리량을 크게 낮추고 응답 시간도 늘어나게 됩니다.4. 운영체제가 MLFQ에서 CPU Bound Process와 I/O Bound Process를 어떻게 구분할까요?OS는 Timeout Interrupt가 일어난 프로세스는 CPU Bound Process로, I/O Burst가 일어난 I/O Bound Process는 I/O Bound Process로 판단합니다. 어떤 프로세스가 할당된 시간 동안 작업을 완수하지 못한 채 타임아웃 인터럽트가 발생하면, 이는 프로세스가 긴 CPU burst를 수행하고 있다는 신호이므로 CPU Bound Process로 간주되어 낮은 우선순위 큐로 이동할 가능성이 큽니다. 반면 Timeout Interrupt 전에 I/O Burst을 발생시키면 CPU를 별로 안 쓴다는 뜻이므로 I/O Bound Process로 판단되어 높은 우선순위 큐에 배치되어 빠른 응답성을 유지합니다.5. 공유자원이란 무엇인가요?여러 프로세스나 스레드가 동시에 접근하고 사용할 수 있는 하드웨어나 소프트웨어 자원을 의미합니다. CPU, 메모리, 파일, 프린터, 네트워크 등이 모든 HW 자원이 여기에 해당할 수 있습니다. 동기화 문제가 발생하지 않도록 주의해야 합니다.6. 교착상태에 빠질 수 있는 조건에는 어떤 것들이 있을까요?상호 배제(Mutual Exclusion), 비선점(No Pre-emption), 점유와 대기(Hold and Wait), 원형 대기(Circular Wait)이 있습니다. 상호 배제는 하나의 프로세스가 자원을 사용 중일 때 다른 프로세스는 해당 자원을 사용할 수 없는 조건입니다. 비선점은 한 프로세스가 자원을 할당받으면, 그 프로세스가 자원을 자발적으로 해제할 때까지 다른 프로세스가 강제로 빼앗을 수 없는 조건입니다. 점유와 대기는 프로세스가 최소한 하나의 자원을 보유한 상태에서, 추가 자원을 요청해 대기하는 상황입니다. 마지막으로 원형 대기 (Circular Wait)란 프로세스들이 점유와 대기 상태로 원형으로 대기하는 것입니다. 예를 들어, 프로세스 A가 프로세스 B가 보유한 자원을 기다리고, 프로세스 B는 프로세스 C의 자원을, 그리고 마지막으로 프로세스 C는 다시 프로세스 A의 자원을 기다리는 형태입니다.

시스템 · 운영체제운영체제

워밍업클럽3기_미션2_고객 조사 설계하기

미션고객 조사 계획을 세워보기. 조사 주제를 설정하고, 어떤 사람들을 어떤 방법으로 조사할지, 어떤 질문을 할지 설계해보고, 인터뷰 질문지까지 만들어 보기 고객 조사 계획(1) 의사결정알림 앱의 A 기능 고도화 이후, A 기능을 완료하는 사용자가 30%에서 10%로 감소하였다. 원인을 파악하고, A 기능을 완료하는 사용자를 전체 방문자의 30%로 증가 시킬 수 있는 방안을 구상한다.(2) 알아야 하는데 모르는 것사용자의 특성: A 기능을 사용하지 않거나, 혹은 사용하는 유저의 특성을 정리한다.신규 사용자의 3%만 A 기능을 활용한다. 외부요인: 기능 외의 외부 요인이 영향을 주지 않았는지 검토한다.자연 검색으로 신규 사용자 유입이 감소하여, 전반적인 기능을 사용하는 유저가 감소하였으나, A 기능의 감소폭이 가장 크다.SNS 로그인 중 페이스북 로그인으로 가입하는 사용자가 큰 폭으로 감소하였다.  기능 A의 프로세스: 기능 A의 시작부터 완료 단계에서 이탈이 가장 많은 단계가 있는지 검토한다.기능 A의 시작 화면에서 개인 설정으로 넘어가는 단계에서 80% 이상의 이탈이 발생한다.코치마크를 추가하는 등의 사후 대처를 하였으나 위의 단계에서 이탈을 막지 못했다.정리신규 사용자의 기능 A 사용 비율이 저조하다.페이스북 로그인 사용자의 이탈 등 외부 요인이 영향이 없진 않지만, 주된 원인으로 보기 어려워 로그인 이슈로 분류하여 따로 대응한다.기능A의 프로세스에서 이탈이 생기는 이유에 대해 파악이 필요하다. (3) 적합한 리서치 방법은 무엇인지 정하기기능 A에 대한 고객의 멘탈모델을 살펴보기 위해 사용성 테스트를 진행한다.(4) 리서치 방법 구체화 하기조사 대상신규 사용자 중 A 기능의 프로세스를 완료하지 않은 신규 사용자 10명 모집 대상 모집 방법A 기능의 프로세스를 완료 단계에 삽입해 둔 이벤트를 기준으로, 신규 사용자 중 해당 이벤트가 발생하지 않은 목록을 추린다.목록에 추려진 신규 사용자가 앱을 방문한 경우, 사용성 테스트에 대한 팝업 알림을 띄워 신청을 받는다.목록에 추려진 신규 사용자 중 휴대폰 번호를 등록한 유저에게 사용성 테스트 신청과 관련한 SNS를 발송한다.테스트 진행 방법테스트는 ZOOM 온라인 미팅으로 진행한다.1차로 대상자 각자 시나리오에 따라 Task를 수행하도록 하고 이를 촬영하여 공유하도록 안내한다.대상자들의 공유 영상을 확인한 이후에 2차로 개별 면담을 진행한다. 테스트 시나리오당신은 기능 A를 활용하여 10분 뒤로 알람을 설정합니다.알람은 10분 동안 울리며, 알람이 울리는 동안 화면을 이동할 수 없도록 설정합니다.인터뷰 질문앱에서 알람 설정을 위해 주로 사용하는 기능은 무엇인가요? 해당 기능으로 알림을 설정하는데 얼마나 걸렸나요? 해당 기능을 주로 사용하는 이유는 무엇인가요?A 기능의 메인 화면에서 한참을 머무르던데, 머뭇거리던 이유가 무엇이었나요?A 기능에서 개인 설정을 마무리하는 과정에서 가장 먼저 설정한 옵션은 무엇이었고, 그 이유는 무엇인가요?  

기획 · PM· PO

Jaeeun Jeong

워밍업 클럽 3기 PM/PO_미션2

미션 2. 고객 조사 설계하기여러분이 맡은 프로덕트에서 고객 조사 계획을 세워보세요. (맡고 있는 프로덕트가 없는 경우, 프로덕트를 하나 정해서 해 보세요) 조사 주제를 설정하고, 어떤 사람들을 어떤 방법으로 조사할지, 어떤 질문을 할지 설계해 보고, 인터뷰 질문지까지 만들어 보세요.조사 배경원생 관리 및 수강 과목등을 관리하는 학원 매니징 서비스의 사용성이 저조함에 원인 파악을 바탕으로 경쟁력 강화를 위한 조사조사 목적사용자가 서비스의 가치를 느끼고 지속적인 서비스 이용 시간을 늘리기 위한 리뉴얼 방안 모집조사 대상서비스에 3개월 이상 접속하지 않은 사용자/ 최근 1개월 이내에 접속한 사용자조사 방법정성 조사 : 각각 10명의 사용자와 1:1 인터뷰정량 조사 : 각각 50명의 사용자에게 설문 조사 진행 질문사용한 학원 매니징 서비스의 기능은 무엇일까요?사용한 학원 매니징 서비스의 사용 기간은 어떻게 되시나요?(몇 개월/ 몇 년 등)학원 관리에 가장 많이 사용하는 기능은 무엇일까요?타사의 학원 관리 서비스를 사용한 적이 있다면 어떤 것일까요? 저희 서비스를 어떻게 알게 되셨나요?저희 서비스를 사용 중단하게 된 주요 이유는 무엇일까요?저희 서비스에서 개선되었으면 하는 부분은 어떤 것일까요?저희 서비스의 디자인이나 사용성에 대해 어떻게 생각하시나요?저희 서비스를 사용함에 있어서 어려웠던 점은 무엇일까요?저희 서비스를 이용함에 있어서 유용하다고 생각했던 점은 무엇일까요? 저희 서비스의 어떤 부분이 리뉴얼된다면 사용 시간이 늘어날 것 같은가요?학원 관리 서비스에서 가장 중요하다고 생각하는 점은 무엇일까요? 

DABBB

[인프런 워밍업 클럽 스터디 3기] 미션 2

미션내가 맡은 프로덕트에서 고객 조사 계획 세우기조사 주제 설정조사 대상 선정질문 설계인터뷰 질문지 만들기조사 대상 서비스용어 설명고객 = 사업자회원 = 고객에게 회비 등 일정 금액을 납부하는 개인고객에게 청구서 생성 및 발송 서비스를 제공하고, 회원의 등록된 결제 정보로 결제를 대행하며, 회원의 수납 여부를 관리해주는 서비스조사 주제 설정신규 고객이 우리 제품을 사용하는 이유에 대해 조사하기로 함사용 이유에 대해 자세히 알게 되면 이를 더 개선하여 신규 고객 유입을 늘릴 수 있을 거라고 예상 조사 대상 선정최근 1개월 내에 가입한 신규 고객 중에서제품의 핵심 기능인 "청구서 생성" 기능을 1번 이상 사용한 고객생성하는 청구서 개수만큼 결제할 요금이 정해짐 -> 유료 고객으로 한정가입 후 빠르게 유료 기능을 쓸 만큼 문제를 해결하고 싶은 기대가 컸던 것으로 봄  질문 설계사업 질문 : 어떻게 하면 신규 고객을 더 많이 획득할 수 있을까?리서치 질문 : 신규 고객은 어떤 문제를 해결하기 위해 우리 제품을 사용하는가?인터뷰 질문 : 우리 서비스에 가입하게 된 결정적 이유는 무엇이었나요? 인터뷰 설문지가입하기 전부터 우리 서비스에 대해 알고 계셨나요?(알고 있었다면) 바로 가입하지 않았던 이유는 무엇인가요?비슷한 다른 솔루션을 사용해보신 경험이 있으신가요?(있다면) 왜 그 솔루션을 선택하셨나요?(있다면) 왜 그 솔루션의 사용을 중단하신 건가요?가입하기 전 망설여지는 부분이 있으셨나요?우리 서비스에 가입하게 된 결정적 이유는 무엇이었나요?사용 후 그 결정적 이유는 해소되었나요?우리 서비스의 어떤 점이 만족스러우신가요?우리 서비스의 어떤 점이 아쉬우신가요?사용하면서 어려웠던 부분이 있으신가요?  

기획 · PM· PO

gptjddl777

[인프런 워밍업 클럽 3기] PM/PO - 2주차 미션

미션 2. 고객 조사 설계하기 여러분이 맡은 프로덕트에서 고객 조사 계획을 세워보세요. (맡고 있는 프로덕트가 없는 경우, 프로덕트를 하나 정해서 해 보세요) 조사 주제를 설정하고, 어떤 사람들을 어떤 방법으로 조사할지, 어떤 질문을 할지 설계해 보고, 인터뷰 질문지까지 만들어 보세요 조사 배경 자사 교육플랫폼을 이용하는 학습자들의 사이트 이용 패턴, 학습방법을 확인해 보니 로그인 후 이미 신청 되어있는 필수로 수강해야 하는 교육만 듣고 빠져 나가는 학습자가 많은 것으로 확인메인홈 → 강의실로만 이동하고 강의(=자사 상품)를 탐색하지 않는 것으로 확인조사 목표 사용자들이 메인 홈에서 강의를 탐색하게 만들기 위한 메인 개편안에 대한 아이디어 수집조사 주제 설정 교육플랫폼 메인 홈에서 강의를 탐색하지 않는 사용자들에 대한 이해조사 대상자 자사 교육플랫폼 계정이 있는 사람 중 강의를 신청해서 들은 횟수가 최근 3개월 내 1회 미만인 대상자조사 방법 정성조사 (사용자 인터뷰) : 15명의 사용자와 1:1 인터뷰 (온/오프라인) 진행정량조사 (설문지) : 100여명 이상 대상자에게 설문조사 진행인터뷰 질문로그인 후 가장 먼저 사용하는 기능, 메뉴는 무엇인가요?메인에서 주로 탐색하는 정보는 무엇인가요?탐색하지 않는다면 그 이유는 무엇인가요?메인에서 가장 관심 있게 보거나 주로 활용하시는 기능 또는 메뉴는 무엇인가요?메인에 큐레이션 되는 콘텐츠를 관심 있게 보시는 편인가요?그렇다면 or 그렇지 않다면 그 이유는 무엇인가요?어떤 메뉴, 경로를 이용해서 강의를 탐색 하시나요?원하는 강의를 찾지 못했을 때는 어떻게 하시나요?메인 홈에 추가되었으면 하는 기능 또는 제안하고 싶으신 의견이 있으실까요?

기획 · PM· PO고객조사설계

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

React의 등장 배경 React는 상태(state) 기반으로 UI를 효율적으로 관리하도록 설계된 라이브러리 기존 자바스크립트의 문제점- DOM을 조작하는 방식이 비효율적 - 변경이 필요할때마다 전체 DOM을 다시 그려야 함 - 전체가 아닌 일부 DOM만 변경시키고 싶으면 복잡한 로직이 필요- 상태 관리의 어려움 - 복잡한 UI에서는 DOM과 데이터의 일관성을 유지하기 어려움 - 데이터와 UI가 양방향으로 상호작용하며 예측하기 어려운 상태가 됨➡ 결과적으로 유지보수가 어렵고, 재사용성이 낮음 React의 해결책- 상태(state) 기반 UI 관리 - useState를 통해 UI가 변경될 때만 렌더링 - batching : 상태를 한번에 모아서 처리- 가상 DOM을 사용한 최소한의 업데이트 - 이전 가상돔과 현재 가상돔을 비교해 필요한 부분만 업데이트 (diff) - 불필요한 DOM 조작을 최소화하여 성능 향상- 컴포넌트 단위 개발 - UI를 작은 단위로 분리하여 재사용 가능 - 각 컴포넌트의 생명주기를 관리할 수 있음 리액트 컴포넌트 생명주기컴포넌트가 생성되고 사용되고 소멸될때까지의 일련의 과정- 마운트 -> 업데이트 -> 언마운트 - 마운트: 컴포넌트를 DOM에 삽입 - 업데이트: 컴포넌트의 props나 state가 변경될 때 발생 - 언마운트: 컴포넌트를 DOM에서 제거 클래스형 컴포넌트 - React.Component를 상속받아 사용- 생명주기별로 메서드가 있으며, 각 메서드를 오버라이드 할 수 있음 함수형 컴포넌트 - 생명주기 메서드를 직접 제공하지 않음.- 훅을 사용하여 생명주기와 유사한 기능 구현 훅(Hook)은 함수형 컴포넌트에서 상태(state)와 생명주기(lifecycle) 기능을 사용할 수 있도록 해주는 React 내장 함수훅의 종류 : useEffect, useState, useMemo, useCallback특히 생명주기에 가장 직접적인 영향을 미치는 훅은 useEffect- useEffect(() => {...}, []) : componentDidMount- useEffect(() => {...}, [state]) : componentDidUpdate- useEffect(() => {{return ()=>{...};}, [state]) : componentWillUnmount  리액트 훅 useState컴포넌트의 상태(state)를 관리하는 Hook- 함수형 컴포넌트에서 상태를 관리할 수 있도록 함- 상태가 변경되면 해당 컴포넌트가 리렌더링됨- useState로 관리하는 값은 **React 내부에서 관리됨- setState를 호출하면 비동기적으로 업데이트(batch 처리) 됨 - batch 처리란? : 리액트 랜더링 최적화 기법중 하나로, 여러번의 setState()호출이 있을때, 각각 개별적으로 랜더링을 발생시키지 않고 한번만 렌더링 되도록 묶어서 처리 상태란 무엇인가?- 사용자의 응답, api 응답 등에 의해 변경되며, ui를 동적으로 변경시키는 핵심 데이터- 리액트는 useState로 상태를 내부적으로 저장하고, 이 상태가 바뀔때 컴포넌트를 리랜더링한다.  상태의 종류- 로컬상태 : 개별 컴포넌트 내부에서 관리되는 상태- 전역상태 : 여러 컴포넌트가 공유하는 상태- 서버상태 : 서버에서 가져오는 상태- UI상태 : UI관련상태 (모달이 열렸는지, 토글이 열렸는지)  useState와 리액트 생명주기- 초기 렌더링 (mounting) → useState의 초기값 설정- 업데이트 (updating) → setState 호출 시 리렌더링됨- 언마운트 (unmounting) → 상태 해제  useEffect컴포넌트의 부수 효과(side effect)를 관리하는 Hook- 함수형 컴포넌트에서 컴포넌트 생명주기를 관리할때 사용됨- API 호출, 이벤트 리스너 등록, DOM 조작 등 비동기 작업을 처리할 때 사용- useEffect는 렌더링 후 실행되며, 의존성 배열(`deps`)을 통해 실행 조건을 제어 가능- 함수를 리턴할 경우, 언마운트 이후 실행됨useEffect(() => {...}); // 매 렌더링마다 실행 useEffect(() => {...}, []); // 마운트 시 1회 실행 useEffect(() => {...}, [count]); // count가 변경될 때만 실행 useEffect(() => { return () => {...}; }, []); // 언마운트 시 실행  useCallback> 함수를 메모이제이션하여 불필요한 리렌더링을 방지하는 Hook- 함수가 매번 새로 생성되는 것을 방지- React.memo()와 함께 사용하여 최적화 가능 - 상태가 변경되거나 props가 변경되면 리랜더링 발생 - 이때 props로 객체나 함수(참조형)을 넘기게 되는 경우 props는 얕은 복사를 진행하기에 매번 리랜더링을 할 수밖에 없음 이때 메모이제이션을 사용하면 불필요한 랜더링 방지 가능 useCallback과 리액트 생명주기- 컴포넌트가 마운트될 때 초기화됨- 의존성 배열이 변경되면 새로운 함수로 업데이트됨 useRef> DOM 요소에 직접 접근하거나, 값이 유지되지만 리렌더링을 유발하지 않는 변수를 관리하는 Hook- DOM 요소를 직접 조작하는 데 사용- 렌더링 간 유지되는 값을 저장할 때도 사용- 값이 변경되어도 컴포넌트가 리렌더링되지 않음 useRef와 리액트 생명주기- 컴포넌트가 마운트될 때 초기화됨- 리렌더링과 무관하게 값을 유지   

프론트엔드워밍업스터디클럽워밍업스터디클럽리액트

징니

인프런 워밍업 클럽 3기 BE 스터디 2주차

💻 강의입문자를 위한 Spring Boot with Kotlin - 나만의 포트폴리오 사이트 만들기 📚 학습@Profile프로필이 default일 때만 DataInitializer 클래스를 생성해 빈으로 등록@Component @Profile(value = ["default"]) class DataInitializer { } kotlin-loggingprintln()을 사용하지 않고, log를 사용하고 싶어서 따로 찾아보고 적용했다kotlin-logging 방식을 적용했고, Kakao Pay 기술 블로그가 도움이 됐다최신 버전Kakao Pay 기술 블로그private val logger = KotlinLogging.logger { } @Component @Profile(value = ["default"]) class DataInitializer(...) { @PostConstruct fun initializeData() { logger.info { "테스트 데이터 초기화" } ...2025-03-14T04:50:10.942+09:00 INFO 12988 --- [ main] c.j.portfolio.domain.DataInitializer : 테스트 데이터 초기화Repository 테스트 코드@DataJpaTest : JPA 관련 테스트를 위한 설정을 제공@TestInstance : 테스트 인스턴스의 라이프사이클을 지정  TestInstance 부분이 이해가 잘 안돼서 따로 찾아보니 이해할 수 있었다@TestInstance 참고Fetch JoinJoin을 활용해 한 번에 부모와 자식 데이터를 조회할 수 있지만 OneToMany, ManyToMany 관계의 자식 Entity가 여러 개일 경우, 하나만 조인할 수 있다는 한계가 있다@Query("select e from Experience e join fetch e.details where e.isActive = :isActive") fun findAllByIsActive(isActive: Boolean): List<Experience>// Fetch Join 적용 전 SIZE = 5 2025-03-16T03:40:41.646+09:00 INFO 2212 --- [ main] c.j.p.d.r.ExperienceRepositoryTest : findAllByIsActive 테스트 시작 Hibernate: select e1_0.id, e1_0.created_date_time, e1_0.description, e1_0.end_month, e1_0.end_year, e1_0.is_active, e1_0.start_month, e1_0.start_year, e1_0.title, e1_0.updated_date_time from experience e1_0 where e1_0.is_active=? 2025-03-16T03:40:41.721+09:00 INFO 2212 --- [ main] c.j.p.d.r.ExperienceRepositoryTest : experiences.size: 5 Hibernate: select d1_0.experience_id, d1_0.id, d1_0.content, d1_0.created_date_time, d1_0.is_active, d1_0.updated_date_time from experience_detail d1_0 where d1_0.experience_id=? 2025-03-16T03:40:41.729+09:00 INFO 2212 --- [ main] c.j.p.d.r.ExperienceRepositoryTest : experience.details.size: 1 Hibernate: select d1_0.experience_id, d1_0.id, d1_0.content, d1_0.created_date_time, d1_0.is_active, d1_0.updated_date_time from experience_detail d1_0 where d1_0.experience_id=? 2025-03-16T03:40:41.731+09:00 INFO 2212 --- [ main] c.j.p.d.r.ExperienceRepositoryTest : experience.details.size: 2 Hibernate: select d1_0.experience_id, d1_0.id, d1_0.content, d1_0.created_date_time, d1_0.is_active, d1_0.updated_date_time from experience_detail d1_0 where d1_0.experience_id=? 2025-03-16T03:40:41.734+09:00 INFO 2212 --- [ main] c.j.p.d.r.ExperienceRepositoryTest : experience.details.size: 3 Hibernate: select d1_0.experience_id, d1_0.id, d1_0.content, d1_0.created_date_time, d1_0.is_active, d1_0.updated_date_time from experience_detail d1_0 where d1_0.experience_id=? 2025-03-16T03:40:41.736+09:00 INFO 2212 --- [ main] c.j.p.d.r.ExperienceRepositoryTest : experience.details.size: 4 Hibernate: select d1_0.experience_id, d1_0.id, d1_0.content, d1_0.created_date_time, d1_0.is_active, d1_0.updated_date_time from experience_detail d1_0 where d1_0.experience_id=? 2025-03-16T03:40:41.741+09:00 INFO 2212 --- [ main] c.j.p.d.r.ExperienceRepositoryTest : experience.details.size: 5 2025-03-16T03:40:41.742+09:00 INFO 2212 --- [ main] c.j.p.d.r.ExperienceRepositoryTest : findAllByIsActive 테스트 종료// Fetch Join 적용 후 SIZE = 5 2025-03-16T03:43:37.319+09:00 INFO 9488 --- [ main] c.j.p.d.r.ExperienceRepositoryTest : findAllByIsActive 테스트 시작 Hibernate: select e1_0.id, e1_0.created_date_time, e1_0.description, d1_0.experience_id, d1_0.id, d1_0.content, d1_0.created_date_time, d1_0.is_active, d1_0.updated_date_time, e1_0.end_month, e1_0.end_year, e1_0.is_active, e1_0.start_month, e1_0.start_year, e1_0.title, e1_0.updated_date_time from experience e1_0 join experience_detail d1_0 on e1_0.id=d1_0.experience_id where e1_0.is_active=? 2025-03-16T03:43:37.369+09:00 INFO 9488 --- [ main] c.j.p.d.r.ExperienceRepositoryTest : experiences.size: 5 2025-03-16T03:43:37.372+09:00 INFO 9488 --- [ main] c.j.p.d.r.ExperienceRepositoryTest : experience.details.size: 1 2025-03-16T03:43:37.374+09:00 INFO 9488 --- [ main] c.j.p.d.r.ExperienceRepositoryTest : experience.details.size: 2 2025-03-16T03:43:37.375+09:00 INFO 9488 --- [ main] c.j.p.d.r.ExperienceRepositoryTest : experience.details.size: 3 2025-03-16T03:43:37.376+09:00 INFO 9488 --- [ main] c.j.p.d.r.ExperienceRepositoryTest : experience.details.size: 4 2025-03-16T03:43:37.376+09:00 INFO 9488 --- [ main] c.j.p.d.r.ExperienceRepositoryTest : experience.details.size: 5 2025-03-16T03:43:37.378+09:00 INFO 9488 --- [ main] c.j.p.d.r.ExperienceRepositoryTest : findAllByIsActive 테스트 종료Batch Fetch SizeIN 절을 사용해 여러 건의 데이터를 한 번에 조회할 수 있지만 한 번에 많은 데이터를 불러오는 것은 애플리케이션이나 데이터베이스에 부담을 줄 수 있기 때문에 적절한 개수 설정이 필요하다// Batch Fetch Size = 10일 경우 ?도 10개 ... project_id in (?,?,?,?,?,?,?,?,?,?) Batch Fetch Size를 적용하기 전에는 detail에 대한 쿼리가 매번 실행됐지만, 적용 후에는 한 번만 실행된다Size는 5인데 Batch Fetch Size를 3으로 두면 detail에 대한 쿼리는 두 번만 실행된다IN 절에 최대 3개까지만 포함pring: jpa: properties: hibernate: default_batch_fetch_size: 10// Batch Fetch Size 적용 전 SIZE = 5 2025-03-17T16:53:55.480+09:00 INFO 4356 --- [ main] c.j.p.d.r.ProjectRepositoryTest : findAllByIsActive 테스트 시작 Hibernate: select p1_0.id,p1_0.created_date_time,p1_0.description,p1_0.end_month,p1_0.end_year,p1_0.is_active,p1_0.name,s1_0.project_id,s1_0.id,s1_0.created_date_time,s1_0.skill_id,s2_0.id,s2_0.created_date_time,s2_0.is_active,s2_0.name,s2_0.skill_type,s2_0.updated_date_time,s1_0.updated_date_time,p1_0.start_month,p1_0.start_year,p1_0.updated_date_time from project p1_0 left join project_skill s1_0 on p1_0.id=s1_0.project_id join skill s2_0 on s2_0.id=s1_0.skill_id where p1_0.is_active=? 2025-03-17T16:53:55.565+09:00 INFO 4356 --- [ main] c.j.p.d.r.ProjectRepositoryTest : projects.size: 5 Hibernate: select d1_0.project_id,d1_0.id,d1_0.content,d1_0.created_date_time,d1_0.is_active,d1_0.updated_date_time,d1_0.url from project_detail d1_0 where d1_0.project_id=? 2025-03-17T16:53:55.573+09:00 INFO 4356 --- [ main] c.j.p.d.r.ProjectRepositoryTest : project.details.size: 1 2025-03-17T16:53:55.574+09:00 INFO 4356 --- [ main] c.j.p.d.r.ProjectRepositoryTest : project.skills.size: 1 Hibernate: select d1_0.project_id,d1_0.id,d1_0.content,d1_0.created_date_time,d1_0.is_active,d1_0.updated_date_time,d1_0.url from project_detail d1_0 where d1_0.project_id=? 2025-03-17T16:53:55.577+09:00 INFO 4356 --- [ main] c.j.p.d.r.ProjectRepositoryTest : project.details.size: 2 2025-03-17T16:53:55.577+09:00 INFO 4356 --- [ main] c.j.p.d.r.ProjectRepositoryTest : project.skills.size: 2 Hibernate: select d1_0.project_id,d1_0.id,d1_0.content,d1_0.created_date_time,d1_0.is_active,d1_0.updated_date_time,d1_0.url from project_detail d1_0 where d1_0.project_id=? 2025-03-17T16:53:55.579+09:00 INFO 4356 --- [ main] c.j.p.d.r.ProjectRepositoryTest : project.details.size: 3 2025-03-17T16:53:55.582+09:00 INFO 4356 --- [ main] c.j.p.d.r.ProjectRepositoryTest : project.skills.size: 3 Hibernate: select d1_0.project_id,d1_0.id,d1_0.content,d1_0.created_date_time,d1_0.is_active,d1_0.updated_date_time,d1_0.url from project_detail d1_0 where d1_0.project_id=? 2025-03-17T16:53:55.588+09:00 INFO 4356 --- [ main] c.j.p.d.r.ProjectRepositoryTest : project.details.size: 4 2025-03-17T16:53:55.589+09:00 INFO 4356 --- [ main] c.j.p.d.r.ProjectRepositoryTest : project.skills.size: 4 Hibernate: select d1_0.project_id,d1_0.id,d1_0.content,d1_0.created_date_time,d1_0.is_active,d1_0.updated_date_time,d1_0.url from project_detail d1_0 where d1_0.project_id=? 2025-03-17T16:53:55.604+09:00 INFO 4356 --- [ main] c.j.p.d.r.ProjectRepositoryTest : project.details.size: 5 2025-03-17T16:53:55.606+09:00 INFO 4356 --- [ main] c.j.p.d.r.ProjectRepositoryTest : project.skills.size: 5 2025-03-17T16:53:55.607+09:00 INFO 4356 --- [ main] c.j.p.d.r.ProjectRepositoryTest : findAllByIsActive 테스트 종료// Batch Fetch Size 적용 후 SIZE = 5 2025-03-17T16:59:17.289+09:00 INFO 19160 --- [ main] c.j.p.d.r.ProjectRepositoryTest : findAllByIsActive 테스트 시작 Hibernate: select p1_0.id,p1_0.created_date_time,p1_0.description,p1_0.end_month,p1_0.end_year,p1_0.is_active,p1_0.name,s1_0.project_id,s1_0.id,s1_0.created_date_time,s1_0.skill_id,s2_0.id,s2_0.created_date_time,s2_0.is_active,s2_0.name,s2_0.skill_type,s2_0.updated_date_time,s1_0.updated_date_time,p1_0.start_month,p1_0.start_year,p1_0.updated_date_time from project p1_0 left join project_skill s1_0 on p1_0.id=s1_0.project_id join skill s2_0 on s2_0.id=s1_0.skill_id where p1_0.is_active=? 2025-03-17T16:59:17.369+09:00 INFO 19160 --- [ main] c.j.p.d.r.ProjectRepositoryTest : projects.size: 5 Hibernate: select d1_0.project_id,d1_0.id,d1_0.content,d1_0.created_date_time,d1_0.is_active,d1_0.updated_date_time,d1_0.url from project_detail d1_0 where d1_0.project_id in (?,?,?,?,?,?,?,?,?,?) 2025-03-17T16:59:17.385+09:00 INFO 19160 --- [ main] c.j.p.d.r.ProjectRepositoryTest : project.details.size: 1 2025-03-17T16:59:17.386+09:00 INFO 19160 --- [ main] c.j.p.d.r.ProjectRepositoryTest : project.skills.size: 1 2025-03-17T16:59:17.387+09:00 INFO 19160 --- [ main] c.j.p.d.r.ProjectRepositoryTest : project.details.size: 2 2025-03-17T16:59:17.387+09:00 INFO 19160 --- [ main] c.j.p.d.r.ProjectRepositoryTest : project.skills.size: 2 2025-03-17T16:59:17.387+09:00 INFO 19160 --- [ main] c.j.p.d.r.ProjectRepositoryTest : project.details.size: 3 2025-03-17T16:59:17.387+09:00 INFO 19160 --- [ main] c.j.p.d.r.ProjectRepositoryTest : project.skills.size: 3 2025-03-17T16:59:17.388+09:00 INFO 19160 --- [ main] c.j.p.d.r.ProjectRepositoryTest : project.details.size: 4 2025-03-17T16:59:17.388+09:00 INFO 19160 --- [ main] c.j.p.d.r.ProjectRepositoryTest : project.skills.size: 4 2025-03-17T16:59:17.390+09:00 INFO 19160 --- [ main] c.j.p.d.r.ProjectRepositoryTest : project.details.size: 5 2025-03-17T16:59:17.390+09:00 INFO 19160 --- [ main] c.j.p.d.r.ProjectRepositoryTest : project.skills.size: 5 2025-03-17T16:59:17.391+09:00 INFO 19160 --- [ main] c.j.p.d.r.ProjectRepositoryTest : findAllByIsActive 테스트 종료// Batch Fetch Size = 3, SIZE = 5 2025-03-17T17:09:35.183+09:00 INFO 944 --- [ main] c.j.p.d.r.ProjectRepositoryTest : findAllByIsActive 테스트 시작 Hibernate: select p1_0.id,p1_0.created_date_time,p1_0.description,p1_0.end_month,p1_0.end_year,p1_0.is_active,p1_0.name,s1_0.project_id,s1_0.id,s1_0.created_date_time,s1_0.skill_id,s2_0.id,s2_0.created_date_time,s2_0.is_active,s2_0.name,s2_0.skill_type,s2_0.updated_date_time,s1_0.updated_date_time,p1_0.start_month,p1_0.start_year,p1_0.updated_date_time from project p1_0 left join project_skill s1_0 on p1_0.id=s1_0.project_id join skill s2_0 on s2_0.id=s1_0.skill_id where p1_0.is_active=? 2025-03-17T17:09:35.245+09:00 INFO 944 --- [ main] c.j.p.d.r.ProjectRepositoryTest : projects.size: 5 Hibernate: select d1_0.project_id,d1_0.id,d1_0.content,d1_0.created_date_time,d1_0.is_active,d1_0.updated_date_time,d1_0.url from project_detail d1_0 where d1_0.project_id in (?,?,?) 2025-03-17T17:09:35.259+09:00 INFO 944 --- [ main] c.j.p.d.r.ProjectRepositoryTest : project.details.size: 1 2025-03-17T17:09:35.260+09:00 INFO 944 --- [ main] c.j.p.d.r.ProjectRepositoryTest : project.skills.size: 1 2025-03-17T17:09:35.261+09:00 INFO 944 --- [ main] c.j.p.d.r.ProjectRepositoryTest : project.details.size: 2 2025-03-17T17:09:35.261+09:00 INFO 944 --- [ main] c.j.p.d.r.ProjectRepositoryTest : project.skills.size: 2 2025-03-17T17:09:35.261+09:00 INFO 944 --- [ main] c.j.p.d.r.ProjectRepositoryTest : project.details.size: 3 2025-03-17T17:09:35.261+09:00 INFO 944 --- [ main] c.j.p.d.r.ProjectRepositoryTest : project.skills.size: 3 Hibernate: select d1_0.project_id,d1_0.id,d1_0.content,d1_0.created_date_time,d1_0.is_active,d1_0.updated_date_time,d1_0.url from project_detail d1_0 where d1_0.project_id in (?,?,?) 2025-03-17T17:09:35.264+09:00 INFO 944 --- [ main] c.j.p.d.r.ProjectRepositoryTest : project.details.size: 4 2025-03-17T17:09:35.265+09:00 INFO 944 --- [ main] c.j.p.d.r.ProjectRepositoryTest : project.skills.size: 4 2025-03-17T17:09:35.265+09:00 INFO 944 --- [ main] c.j.p.d.r.ProjectRepositoryTest : project.details.size: 5 2025-03-17T17:09:35.265+09:00 INFO 944 --- [ main] c.j.p.d.r.ProjectRepositoryTest : project.skills.size: 5 2025-03-17T17:09:35.266+09:00 INFO 944 --- [ main] c.j.p.d.r.ProjectRepositoryTest : findAllByIsActive 테스트 종료아쉬운 점금요일부터 이번 주 강의를 듣게 됐는데 생각보다 오래 걸려서 섹션 4까지 강의를 다 못 들은 게 아쉽다 오래 걸린 이유늦게 듣기 시작강의를 들으면서 부족한 내용 구글링 및 보완Trouble Shooting 내용 정리 다음 주는 진도가 뒤처지지 않도록 매일 들어야겠다보완할 점아직은 Kotlin으로 Project를 하는 것이 낯설고 어렵게 느껴진다Kotlin 문법에 얼른 익숙해지도록 공부해야겠다강의 매일 듣기Kotlin 문법 공부Kotlin Spring Project 찾아보기혼자 테스트 코드 작성해보기Trouble Shooting/h2-console 접속 오류findById 최적화 궁금증 해결회고테스트 코드를 작성하는 건 역시 어려운 것 같다Java Spring Project를 했을 때도 테스트 코드를 작성하는 것은 어려웠다일단 이번 주에 작성했던 Repository 테스트 코드를 다시 살펴보고, 테스트 코드를 잘 작성하려면 어떻게 공부를 해야 하는지 질문을 남겨봐야겠다이번 주는 유독 어려웠던 주차였지만, 동시에 좀 더 성장할 수 있었던 주차였다스터디 수료 후에는 테스트 코드와 친해져 있었으면 좋겠다  🎯 미션 2테이블 설계하기ERD, 표 등 테이블 설계한 내용을 readme 파일에 작성커밋 메시지 : [미션2] 테이블 설계하기미션2 제출 스레드에 깃허브 커밋 링크를 공유문제학사 관리 서비스를 주제로 선택했을 때, 학생이 여러 과목을 수강 신청할 수 있는 기능만 CRUD로 구성해 가볍게 미니 프로젝트를 진행하려고 했다하지만 수강 신청 기능만 봤을 때 U 부분을 어떻게 해야 할지 많이 고민 됐다C : 수강 신청R : 수강 신청 목록 조회D : 수강 신청 취소결국, 처음 의도와 달리 기능이 확장되면서 테이블도 증가하게 되었다그리고 설계를 하면서 각 테이블 컬럼도 증가하게 되었다2T → 4T해결 도전해결은 아니지만, 설계한 대로 진행해 보려고 한다Kotlin은 처음이지만, Spring Project는 처음이 아니기 때문에 Kotlin을 사용해 학사 관리 서비스의 기능 구현을 마무리하는 것을 이번 미니 프로젝트의 목표로 삼았다회고처음 주제를 선택했을 때, 기능 구현에 대해 단순하게 생각했다그 결과, 테이블을 설계할 때 처음 의도와는 다른 구조가 만들어졌다주제에 따른 기능을 충분히 검토하고, 기한 내에 구현할 수 있는 기능인지 고민했어야 했는데, 한편으로는 자만했던 부분도 있었다Kotlin 문법만 금방 익히면, Spring 프로젝트는 처음이 아니었기 때문에 추후 기능이 확장되더라도 문제없을 거라고 안심했던 것 같다  🎯 미션 3REST API 설계하기API를 설계한 내용을 readme 파일에 작성한 뒤 커밋커밋 메시지 : [미션3] REST API 설계하기미션3 제출 스레드에 깃허브 커밋 링크를 공유문제세부 기능이 정리가 안돼 설계할 때 수정을 자주 했다해결권한별로 기능을 분리하니 이전보다 명확해져 세부 기능을 정리하기가 쉬웠다덕분에 URL을 설정하는 것도 훨씬 수월해졌다공통 기능학생 기능prefix: /students교수 기능prefix: /professors관리자 기능prefix: /admins 회고보통 도메인별로 기능을 정리해 설계하는 것 같고, 나도 항상 그렇게 해왔다하지만 이번 서비스에서는 각 권한에 따른 역할이 뚜렷하기 때문에 권한에 따른 URL 경로를 설정하는 방식으로 하였고, 권한별로 분리하니 각 기능이 직관적으로 보였다 서비스가 커지면 권한에 따라 기능이 세분화되어 URL 경로가 복잡해질 수 있고, 오히려 마이너스가 될 수 있다고 생각했다주요 기능이 소수이되 규모가 작은 미니 프로젝트인 경우에만 개인적으로 사용해야겠다

백엔드

[인프런 워밍업 클럽 3기] PM/PO 2주차 과제

미션2. 고객 조사 설계하기 조사 대상 서비스:가상의 음악 스트리밍 서비스 '일일추천음악'무엇을 개선하기 위해 조사를 하려고 하는가:'일일추천음악'의 추천 시스템의 사용성와 추천 방식을 개선하기 위함임 인터뷰를 통해 얻고자 하는 내용:- '일일추천음악' 유저들의 사용 경험을 조사하고, 조사한 사용경험을 바탕으로 '일일 추천 음악'의 추천 알고리즘을 개선하고자 함 인터뷰 방식:1. 심층 인터뷰 (10명)- '일일 추천 음악'을 매일 접속하고, 4시간 이상 음악을 듣는 유저들을 대상으로 인터뷰를 진행함2. 정성 인터뷰 (1000명)- '일일 추천 음악'을 3개월 이상 구독하고 있는 유저들에게 랜덤하게 팝업 형태로 설문조사 페이지를 띄워, 조사를 진행함 인터뷰 질문: 정성 인터뷰 - '일일 추천 음악' 서비스를 언제부터 이용하셨나요?- '일일추천음악' 서비스는 어떤 때 이용하시나요?- 타 경쟁 서비스에 비해서 '일일추천음악'은 어떤 강점을 지니고 있나요? - 반대로 '일일추천음악'에서 보완해야 하는 사항이 있을까요? 심층 인터뷰 (정성 인터뷰와 동일한 질문으로 진행)- '일일추천음악' 추천 시스템을 얼마나 사용하고 계신가요?- (적게 이용하고 있다 라고 말한다면,) 추천 시스템을 이용하지 않는 이유가 있으신가요? - (많이 이용하고 있다 라고 말한다면,) 추천 시스템을 이용하시면서 불편하신 점이 있으셨나요?

채널톡 아이콘