코드리뷰 서비스를 만들었다

Codex 리뷰가 좋았는데 횟수가 모자랐다. 그래서 직접 만들었다.


Codex 코드리뷰 서비스를 잘 쓰고 있었는데 Plus 플랜의 제한이 빡빡했다. (물론 현재 claude code max 5x 와 codex pro 두개를 사용중이다.) OAuth로 직접 호출하면 제한 없이 쓸 수 있지 않을까? 이 생각 하나로 시작한 프로젝트가 사내 Git 플랫폼이 바뀌면서 본격적인 서비스가 됐다.

처음엔 프롬프트도 대충이었다. 일단 되는지가 목표였고 프롬프트는 차근차근 개선하면 된다고 생각했다. 실제로 그렇게 됐다.


Codex 리뷰 서비스가 좋았다

사내에서는 AI 관련 지원이 없었다. AI 코드리뷰를 쓰고 싶으면 개인적으로 알아서 해야 하는 상황이었다. CodeRabbit 같은 서비스를 팀에 도입하려고 알아봤는데 비용이 만만치 않아서 무산됐다. 최근에는 Claude도 코드리뷰 서비스를 출시했지만 역시 비싸다고 한다. GitHub Actions에 AI 리뷰를 붙이는 방법도 있긴 한데 팀에서 쉽게 적용하고 관리할 수 있는 형태는 아니었다.

자세히 보기

AI Agent 학습: 메모리와 Function Calling 구현


AI Agent란 무엇인가?

기본 개념

AI Agent는 단순히 LLM(Large Language Model)이 아닙니다. 정확한 정의는 다음과 같습니다:

1
AI Agent = LLM + 도구(함수) + 실행 루프

중요한 사실: LLM은 텍스트를 예측하는 엔진일 뿐이며, 실제로 코드를 실행하거나 외부 시스템과 상호작용할 수 없습니다.

그렇다면 ChatGPT가 계산을 하거나 웹 검색을 하는 것처럼 보이는 이유는? 바로 Function Calling 덕분입니다.

자세히 보기

함수 빌더 패턴(Function Builder Pattern): React 테이블 셀 렌더링을 “설정 가능한 함수”로 표준화하기

“컬럼마다 조건이 다르고, 스켈레톤/빈값/포맷팅도 섞여 있는데… 매번 컴포넌트를 새로 짜야 할까?”

실무에서 테이블·리스트 셀을 많이 다룰수록 공감하셨을 질문입니다.

아래 원본 문서를 바탕으로 옵션을 받아 ‘특화된 렌더링 함수’를 만들어내는 함수 빌더 패턴을 그대로 정리해 드립니다.


패턴 개요

함수 빌더 패턴은 설정(config)을 받아 특정 목적에 맞게 구성된 함수를 반환하는 고차함수(HOF) 패턴입니다. 전통적 팩토리 패턴의 함수형 버전으로, 객체 대신 ‘함수’를 생성합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
// 함수 빌더 시그니처
type FunctionBuilder<TConfig, TFunction> = (config: TConfig) => TFunction;

// 실제 구현
const createGenericTextRenderer = <TRow extends BaseRowWithSkeleton>(
defaultOptions: Partial<RendererOptions> = {},
): SkeletonAwareRenderer<TRow, string | undefined> => {
// 설정을 클로저에 캡처
return (value, row, options = {}) => {
const mergedOptions = {...defaultOptions, ...options};
// 실제 렌더링 로직
};
};

핵심 개념

  • 두 단계 실행: (1) 빌드 단계에서 옵션을 고정 → (2) 실행 단계에서 값/행을 받아 렌더
  • 클로저 활용: 빌드시 캡처한 설정을 실행 시점에도 사용
  • 부분 적용: 매개변수 일부를 미리 고정하고, 나머지는 실행 시 주입
자세히 보기

함수형도 싱글톤 되잖아요? 그럼에도 클래스를 택한 이유

“함수형도 싱글톤 만들 수 있는데요?”
맞습니다. 기술적으로 가능합니다.
하지만 저는 단순히 ‘싱글 인스턴스’를 만드는 것만으로는 부족했습니다.
본 글은 지난 “왜 클래스 기반 ZebraLabelService를 선택했는가” 글에 이은 후속 글 입니다.


함수형도 싱글톤이 될 수 있습니다

사실 함수형으로도 싱글톤은 만들 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 함수형 싱글톤 패턴 (즉시 실행 함수)
const PrinterService = (() => {
let isInitialized = false;

return {
async initialize() {
if (!isInitialized) {
// 초기화 작업
isInitialized = true;
}
},
async print() {
if (!isInitialized) throw new Error('초기화 필요');
// 인쇄 작업
},
cleanup() {
isInitialized = false;
},
};
})();

이렇게 구현하면 모듈 자체가 싱글 인스턴스처럼 동작하므로 기본적인 싱글톤 조건은 충족합니다.


자세히 보기

왜 클래스 기반 ZebraLabelService를 선택했는가

Zebra 라벨 프린터 통합: 왜 클래스 기반 ZebraLabelService를 선택했는가

“React 생태계에서 굳이 클래스를?”
하드웨어(프린터)를 다룰 때는 이야기가 달라집니다. 단일 연결, 복잡한 초기화, 상태/리소스 수명 주기… 이 모든 것을 안전하고 일관되게 관리해야 합니다.
본 글은 실제 프로덕션에서 Zebra 라벨 프린터를 통합하며 내린 결정—클래스 기반 싱글톤 서비스—의 이유와 구현을 원문 코드 중심으로 정리한 내용입니다.


문제 정의와 선택 배경

Zebra 라벨 프린터 통합은 하드웨어라는 단일·희소 자원을 다룹니다. 동시에 웹 앱(특히 React)에서는 화면 전환/재렌더링, 비동기 초기화, 중복 호출이 일어나기 쉽죠.
다음 요구사항이 핵심이었습니다:

  • 싱글 연결: 물리 프린터는 사실상 단일 연결/세션 가정이 안전
  • 상태 일관성: 앱 전역에서 동일한 프린터/연결 상태 공유
  • 초기화 중복 방지: 비동기 초기화 경쟁 조건(Race Condition) 제거
  • 명확한 라이프사이클: 초기화 → 사용(출력) → 정리의 수명 주기 캡슐화

따라서 클래스 기반 싱글톤 서비스가 자연스러운 해법.

자세히 보기

FECConf 2025 다녀와서, 드디어 블로그를 시작하기로 했다

발표가 끝나자마자 노트북을 꺼내 메모장을 열었다. “이제 정말 시작해야겠다”는 생각이 머리를 스쳤다.
2025년 8월 23일 토요일, 처음으로 찾은 FECConf 2025 현장은 에너지와 열정으로 가득했다. 발표를 듣는 순간보다도, 세션이 끝나고 스피커 혹은 리더분들과 이야기를 나누는 순간순간이 더 오래 남았다.

컨퍼런스에서 얻은 가장 큰 깨달음

FECConf는 항상 개발자로서 한 단계 성장할 수 있는 계기를 주지만, 이번에는 조금 달랐다. 좋은 경험을 가진 리더분들과 대화하면서 느낀 건 꾸준히 쌓아가는 습관이 결국 나를 만든다는 것이었다.
기술 스택이나 최신 트렌드보다도 매일 조금씩 기록하고 나누고 돌아보는 과정이 진짜 성장을 만든다는 점을 실감했다.

누군가는 “짧은 글이라도 꾸준히 쓰다 보면 어느 순간 내 글이 다른 사람에게 힘이 된다”고 했다. 그 말이 크게 와 닿았다. 나 역시 지난 몇 년간 다양한 경험을 쌓아왔지만, 그 경험을 흘려보내는 데 그치지 않고 기록으로 남겼다면 지금쯤은 더 단단한 기반을 만들 수 있었을 거다.

블로그를 시작하는 이유

그래서 나는 결심했다. 내 경험과 지식을 글로 정리해보자.

  • 그동안 프로젝트에서 겪은 시행착오
  • 새로운 기술을 도입하며 배운 점
  • 개발자로서 성장하며 느낀 고민들
자세히 보기
[JS] ES2022 신기능 중 4가지

[JS] ES2022 신기능 중 4가지

이채현

노션에서 보기

Top level await

기존 await를 사용하려면 async 함수 내에서만 가능했다.

1
2
3
(async function () {
await foo();
})();

하지만, ES2022부터 이러한 규칙이 사라지고, awaitasync함수없이 모듈에서 아래와 같이 작성가능해졌다.

자세히 보기
[JS] ESLint 알고쓰기 : 설정 설명

[JS] ESLint 알고쓰기 : 설정 설명

이채현

노션 링크

이 문서는 eslint.org를 참고하여 eslint 7.32.0 버전에서 작성되었으며, 아래 프로젝트를 기반으로 작성했습니다.

https://github.com/chlee1001/react-typescript-simple-boilerplate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
{
"env": {
"browser": true,
"es2021": true,
"node": true
},
"extends": [
"airbnb",
"airbnb-typescript",
"airbnb/hooks",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"plugin:import/recommended",
"plugin:import/typescript",
"plugin:jsx-a11y/recommended",
"plugin:prettier/recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": [
"./tsconfig.json"
],
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": [
"react",
"@typescript-eslint",
"import"
],
"rules": {
"import/prefer-default-export": "off",
"import/no-unresolved": 0,
"import/extensions": [
"off"
],
"react/function-component-definition": [
2,
{
"namedComponents": "arrow-function",
"unnamedComponents": "arrow-function"
}
]
},
"ignorePatterns": [
"paths.js",
"webpack.*.js",
"dist/*",
"node_modules/*"
],
"settings": {
"react": {
"version": "detect"
}
}
}

자세히 보기
[JS] 선언한 모듈로 이동하기 (alias) - Go to declaration

[JS] 선언한 모듈로 이동하기 (alias) - Go to declaration

이채현

문제

많은 사람들은 선언한 모듈들을 command/ctrl + click으로 해당 파일로 바로 이동하거나 자동완성이 되게하는 IDE나 Editor의 기능을 사용할 것이다. 그리고 babel-plugin-module-resolver을 통해 모듈의 경로를 별칭으로 바꿔서 사용할 것이다. 하지만 별칭으로 바꾸면서 위 기능이 깨지는 문제가 종종 있다. 그리고 이 문제는 플러그인쪽에서는 해결되지 않고 있다. npm에 올라온 최신버전은 이미 2년이 지났다.

babel-plugin-module-resolver

해결

우리는 jsconfig.json을 사용하여 IDE가 사용자 지정 resolve규칙을 따르도록 하는 것이 좋다. 이 접근 방식은 WebstormVS Code 모두 작동한다.

자세히 보기
[JS] babelrc와 webpack.config

[JS] babelrc와 webpack.config

이채현

Webpack으로 React 프로젝트를 초기 설정하다가 ,

1
2
3
4
5
6
7
8
9
10
11
12
presets: [
[
'@babel/preset-env',
{
targets: {
browsers: ['> 5% in KR', 'last 2 chrome versions'],
},
debug: true,
},
],
'@babel/preset-react',
],

위 코드의 presets가 과연 .babelrc에 있어야 하는지, webpack.config.js에 있어야하는지 잘 모르겠어서 각 파일의 목적을 정리해보았다.

babelrc

.babelrcbabel의 설정을 위해 사용한다.

자세히 보기