1️⃣ 오늘의 Keyword
2️⃣ 학습 내용 및 예시
AI에게 코틀린 공부를 하여 작성했던 노트를 모두 전달.
아래와 같은 주문으로 예제 제작 요청
이건 내가 일주일 동안 코틀린 공부를 시작하고 배운 개념들을 노트한거야.
이 데이터와 아래의 지침을 기반으로 예제를 만들어줘
- 배운 개념들을 실습하기 위한 목적
- 모든 개념을 적용할 필요는 없음
- 억지로 어렵게 예제를 만들 필요는 없고, 주로 자주 쓰는 것들 위주로 사용
- 단, 아직 배우지 않은 것들은 왠만하면 사용하지 말것.
- 하나의 프로그램을 만드는 것으로 간단한 예제를 만들어줘.
- 아키텍처는 아스키 아트로 보여주고, 작업 순서만 알려줘.
- 물어보기 전에는 코드를 보여주지 말것
아키텍쳐 제안을 보고 이해가 안되는 부분, 추가해야할 기능들을 정리
┌─────────────────────────────────────────────────────────────┐ │ App(Main) │ │ - while loop │ │ - readLine() / 입력 파싱 │ │ - when(menu) 분기 │ │ - try/catch 로 에러 메시지 출력 │ └───────────────┬─────────────────────────────────────────────┘ │ (호출) v ┌─────────────────────────────────────────────────────────────┐ │ MenuController │ │ - 화면에 메뉴 출력 │ │ - "메뉴 번호 → 동작" 라우팅 │ │ - 사용자 입력을 서비스 호출에 맞게 변환 │ │ - 입력이 비었을 때 기본값 적용(기본 인수/?:) │ └───────────────┬─────────────────────────────────────────────┘ │ │ calls v ┌─────────────────────────────────────────────────────────────┐ │ HabitService │ │ Use-cases(기능) │ │ 1) addHabit(name, goalPerWeek=3, memo? , vararg tags) │ │ 2) listHabits() │ │ 3) checkIn(habitIndex, memo?) │ │ 4) showStats() │ │ │ │ - 비즈니스 규칙(예: 오늘 중복 체크 금지) │ │ - 계산(연속 streak / 포인트) │ │ - 잘못된 입력이면 throw │ └───────────────┬───────────────────────────────┬─────────────┘ │ │ │ uses │ uses v v ┌─────────────────────────────────┐ ┌──────────────────────┐ │ Validators │ │ DateProvider │ │ - Habit 이름 빈값/길이 검사 │ │ - 오늘 날짜 제공 │ │ - goalPerWeek 범위 검사 │ │ (테스트/고정값 가능) │ │ - index 범위 검사 │ └──────────────────────┘ │ - 메모 길이 검사(선택) │ │ -> 실패 시 throw │ └─────────────────────────────────┘ | | data access v ┌─────────────────────────────────────────────────────────────┐ │ HabitRepository (in-memory) │ │ Storage │ │ - habits: MutableList<Habit> │ │ - checkIns: MutableList<CheckIn> │ │ │ │ Functions │ │ - addHabit(habit) │ │ - getHabits(): List<Habit> │ │ - getHabitOrNull(index): Habit? │ │ - addCheckIn(checkIn) │ │ - findCheckInsByHabit(id): List<CheckIn> │ │ - findCheckInsByHabit(habitId): List<CheckIn> │ │ - findCheckInsByHabitInRange(habitId, from, to): List<CheckIn>│ │ - existsTodayCheckIn(id, today): Boolean │ └────────────────────────────────────────────────────────────────┘ ^ │ │ stores/returns │ ┌─────────────────────────────────────────────────────────────┐ │ Models │ │ Habit │ │ - id (Int) │ │ - name (String) │ │ - goalPerWeek (Int) │ │ - memo (String?) │ │ - tags (List<String>) │ │ - createdAt (날짜/문자열) │ │ - init { 유효성 검사 } │ │ │ │ CheckIn │ │ - habitId (Int) │ │ - date (날짜/문자열) │ │ - memo (String?) │ └─────────────────────────────────────────────────────────────┘
Top-Level 변수, 클래스, main함수 생성
- 상수, enum 클래스를 먼저 선언
- Habit 클래스, CheckIn 클래스의 주 생성자 작성
- habitList, checkInList를 가변타입으로 선언
- main 함수를 만들어 while로 프로그램을 반복설정
main함수 내에 출력과 메뉴 구조를 작성
- 메인메뉴 출력부분은 PromptOnScreen 클래스를 만들어
mainMenu 멤버함수로 선언
- main 함수의 반복문 안에 메인메뉴 출력 생성
- when을 switch처럼 사용해 사용자입력값을 nullable 처리하고
enum으로 치환하고 각각의 기능 함수가 실행되도록 작성
각 메뉴 기능 함수를 선언 및
첫번째. 습관 추가 메뉴 함수 작성
- addHabit 함수를 선언하는 과정에서 필요하다고 판단해
‘습관 이름 길이’, ‘주목표 횟수’, ‘메모의 이름 길이’의 제한을 상수로 추가.
- top-levle에 최초에 입력되어있을 Habit 객체를 만들어 Array로 담고, 스프레드 연산자로
habitList에 담기.
- 추가할 습관의 이름 입력, 주간 목표횟수 입력, 메모의 입력, 태그 입력을 각각 loop화 해서
알맞은 데이터가 입력 될때까지 각 구간을 반복설정. 그리고 결과적으로 수집한 입력데이터로
새로운 취미 객체를 생성하는 구조까지 설계.
- <<그 사이에 배운점 - 중요**>>
inputName = readlnOrNull()?.trim() ?: "" //-1 같은 Int로 null을 치환하는 것은 좋지 않다. when { inputName == -1 -> println("잘못 된 입력입니다.") }
readlnOrNull() →사용자 입력의 문자열이 있으면 그 문자열, 없으면 null.
그런데 엘비스 연산자로 -1을 기본값으로 넣으면 어떻게 되느냐.
inputName에 사용자 입력값을 담아야하는데, 문자열이 입력되면 String, 입력이 없으면 -1 이라는 정수를 담아야하니, inputName을 Any 타입으로 올린다.
그렇게 되면 when이나 if문으로 입력값 조건으로 사용할 때, String의 성질을 잃어버려
.length, .trim(), .isEmpty() 같은 함수를 사용 할 수 없다.
즉 의미를 분기할 때에 다른 타입으로 치환하지 말라!!!
사실 위의 내용보다 더 중요한건, 코틀린에서는 가능하다면
에러 상태를 “값”으로 표현하지 마라.
에러 상태는 null/분기/예외로 표현하라. 이것이 더 중요함
왜냐하면 값으로 의미를 분기시키면 규칙이 늘어날수록, 값 판별인지 에러판별인지
헷갈리기 시작한다.
그런데 엘비스 연산자로 -1을 기본값으로 넣으면 어떻게 되느냐.
inputName에 사용자 입력값을 담아야하는데, 문자열이 입력되면 String, 입력이 없으면 -1 이라는 정수를 담아야하니, inputName을 Any 타입으로 올린다.
그렇게 되면 when이나 if문으로 입력값 조건으로 사용할 때, String의 성질을 잃어버려
.length, .trim(), .isEmpty() 같은 함수를 사용 할 수 없다.
즉 의미를 분기할 때에 다른 타입으로 치환하지 말라!!!
사실 위의 내용보다 더 중요한건, 코틀린에서는 가능하다면
에러 상태를 “값”으로 표현하지 마라.
에러 상태는 null/분기/예외로 표현하라. 이것이 더 중요함
왜냐하면 값으로 의미를 분기시키면 규칙이 늘어날수록, 값 판별인지 에러판별인지
헷갈리기 시작한다.
즉 아래와 같이 코드를 작성하는 것이 올바르다
var inputName: String? //nullable 타입으로 inputName = readlnOrNull()?.trim() //null 값에 기본값을 만들지 않고 when { inputName == null -> println("잘못 된 입력입니다.") //null 자체를 예외 분기로 처리 }
- 기능이 필요해 검색해서 알아낸 새로운 함수
- split 함수 (String → List<String>)
”study, language, health”를 구분자 “ ,”로 잘라서
[”study”, “language”, “health”] 이렇게 리스트에 담아줌 - map 함수 (List의 “모든 요소 변환”)
[” study”, “language ”, “ health”] 이 리스트의 요소들은
.map {it.trim()} 의 결과
["study", "language", "health"] 로 앞뒤 공백을 잘라줌.
여기서 {it.trim()} 은 “각 요소(it)에 trim을 적용해서 바꿔줘” 라는 뜻. - filter 함수 (List에서 “조건에 맞는 것만 남김”)
[”study”, “”, “health”, “”]
.filter { it.isNotEmpty() } 의 결과
["study", "language"] 로 값이 있는 것만 남김
두번째. 습관 목록 메뉴 작성
- PromptOnScreen 클래스에 습관목록 출력 함수 선언
- 사용자 입력 나가기 선택 생성

