[카테고리:] 개발

  • 면접 복습


    2026.08.19, 21:51


    제가 발전해볼게요…

    js ts tsx 차이

    JS (.js)자바스크립트. 타입 검사 없음
    JSX (.jsx) JavaScript XML자바스크립트 안에서 <div>, <button> 같은 HTML 유사 마크업 문법을 쓸 수 있게 해주는 확장 문법. React가 만든 개념. UI 구조를 코드로 표현할 때 사용.
    TS (.ts)타입스크립트. JS + 정적 타입 시스템. 컴파일 시 타입 오류 검출
    TSX (.tsx)TS + JSX 문법 허용. React에서 <div>..</div> 같은 마크업을 TS 코드 안에 쓸 때 사용. TSX는 TS의 특수 확장자로 컴포넌트 파일에서만 필요.
    // jsx
    // 브라우저가 직접 이해하는 문법이 아님
    // Babel 같은 컴파일러가 순수 JS로 변환
    const element = <h1>안녕하세요</h1>;
    
    // 내부적으로 다음과 같은 형태로 변환
    React.createElement('h1', null, '안녕하세요')

    순수 로직 파일(유틸 함수 등)은 .ts, UI 렌더링이 들어간 파일은 .tsx

    Selenium이란

    웹 브라우저를 코드로 제어해 UI 자동화 테스트를 수행하는 오픈소스 프레임워크

    실제 브라우저(Chrome, Firefox 등)를 구동해 클릭, 입력, 페이지 이동 등을 시뮬레이션

    WebDriver API를 통해 여러 언어(Java, Python, JS 등)에서 제어 가능

    E2E(End-to-End) 테스트, 크롤링/스크래핑에도 활용

    단점: 실행 속도 느림, 유지보수 비용 높음 (요소 선택자 변경에 취약)
    -> 최근엔 Playwright, Cypress가 대안으로 많이 쓰임

    Playwright

    Microsoft가 개발한 오픈소스 브라우저 자동화 라이브러리

    Selenium과 목적은 같지만(브라우저 제어), 설계 철학과 아키텍처가 훨씬 최신

    E2E 테스트 프레임워크

    Selenium과 달리 auto-wait가 내장되어 대기 로직을 직접 짤 필요가 없다

    Locator 기반 설계로 요소를 동작 시점에 평가해 타이밍 이슈에 강하다

    시맨틱 셀렉터를 지원하여 Selenium의 셀렉터 취약성 문제를 완화

    WebDriver API

    브라우저를 외부에서 프로그래밍적으로 제어할 수 있게 해주는 표준 인터페이스(프로토콜)

    E2E 테스트

    End-to-End 테스트

    사용자가 실제로 경험하는 흐름 전체를 처음부터 끝까지 검증하는 테스트

    전체 시나리오를 한 번에 검
    예: 회원가입 -> 로그인 -> 상품 장바구니 담기 -> 결제

    크롤링 (Crawling)

    웹을 자동으로 돌아다니며 페이지를 수집하는 행위

    검색엔진(구글 봇)이 웹 전체를 인덱싱하는 것이 대표적 예

    페이지 목록/URL 자체가 목적인 경우가 많음

    스크래핑 (Scraping)

    특정 페이지에서 원하는 데이터를 추출하는 행위

    예: 쇼핑몰 상품 페이지에서 가격, 재고 정보만 뽑아내기

    크롤링이 탐색 엔진이라면 스크래핑은 데이터 파서에 가깝다

    하네스 엔지니어링이란 (Harness Engineering)

    참고자료: 하네스 엔지니어링 15분 만에 이해시켜드립니다 | 개념, 핵심 원칙, AI 에이전트

    하네스

    원래 뜻은 (말의) 마구(말을 타거나 부리는 데 쓰는 기구; 굴레·안장·재갈 등)

    말(AI 모델)은 힘이 세지만 통제 장치(하네스)가 없으면 엉뚱한 방향으로 간다

    하네스 엔지니어링의 정의

    하네스를 체계적으로 설계하고 개선하는 기술

    하네스 엔지니어링이 부상한 이유

    • AI 모델의 상향 평준화 -> 모델을 둘러싼 시스템이 차이를 만든다
    • 에이전트가 데모에서 실무로 이동
    • 개념에 이름이 붙음

    하네스 엔지니어링이라는 이름

    미쉘 하시모토(하시코프 공동창업자, 테라폼 제작자)가 2026년 2월 블로그에서 사용

    오픈AI가 100만 줄 실험을 같은 제목으로 발표하여 업계 전체로 용어가 퍼진다.

    프롬프트 엔지니어링, 컨텍스트 엔지니어링, 하네스 엔지니어링

    프롬프트 엔지니어링

    • 뭘 말할까
    • (이메일 비유) 기깔나는 메일을 쓰기
    • 한계: 단발성 질의응답에 유효하나 장시간 복잡한 작업에 한계가 있다

    컨텍스트 엔지니어링

    • 뭘 보여줄까
    • (이메일 비유) 첨부파일 붙이기
    • 안드레아 카파시
    • 한계: 여전히 한번의 작업·한 번의 세션 단위에 머문다

    하네스 엔지니어링

    • 어떤 환경에서 일하게 할까
    • (이메일 비유) 그 사람이 일하는 사무실 전체(책상 배치·업무 규칙·보고 체계·교정 프로세스)를 설계
    • 하네스가 앞의 둘을 포함한다

    하네스를 이루는 네 기둥

    • 컨텍스트 엔지니어링: 컨텍스트에 없는 정보는 존재하지 않는 것과 같다
    • 아키텍처 제약: 좋은 코드를 ‘프롬프트(말)로 부탁’하는 대신 ‘기계적으로 강제’한다
    • 피드백 루프: AI는 자기 결과물을 스스로 제대로 평가하지 못한다
    • 엔트로피 관리: 정리 전담 에이전트를 주기적으로 돌린다

    1000페이지짜리 매뉴얼이 아니라 지도를 줘라

    오픈AI가 거대한 지시 파일 실패 후 내린 결론

    규칙을 한 파일에 몰아넣지 말고 분리한 뒤 그 위치만 안내한다

    마틴 파울러의 두 장치 – 가이드와 센서

    가이드: 에이전트가 행동하기 전에 방향을 잡아주는 장치

    센서: 행동한 후에 결과를 체크해 교정하는 장치

    둘 다 있어야 제대로 된 하네스

    피드백 루프 (Feedback Loop)

    에이전트의 판단을 자연어 주장이 아니라 실행 가능한 증거로 검증

    규칙을 시스템으로 강제할 때 사용할 수 있는 도구

    린터 (Linter): 코드 실행 없이 문법 오류, 스타일 위반, 잠재적 버그를 미리 잡아주는 정적 분석 도구

    테스트 (Test): 코드가 의도한대로 동작하는지 검증하는 코드

    CI (Continuous Integration, 지속적 통합): 코드를 push/PR할 때마다 자동으로 빌드·린트·테스트를 실행해주는 자동화 시스템

  • [ASM] 42 Libasm 과제를 해보아요~(9) ft_read


    2026.08.16, 18:46


    아무도 안 궁금했던 근황: 보조강사 알바, 8/7 정보처리기사 필기 시험, 8/9 독학사 컴공 3단계 시험, 포트폴리오 만들어서 정규직으로 써달라고 메일 보내기

    (바빴다는 뜻)

    아무튼 ft_read를 만들어볼거예요~~

    ft_read입니다

    ft_read는 ft_write와 거의 대칭이지만, 함정이 하나 다릅니다.

    구분fdbufcountsyscall 번호
    ft_writerdirsirdx1
    ft_readrdirsirdx0

    syscall 번호와 에러 처리(push raxcall __errno_location wrt ..pltpop qword [rax]mov rax, -1) 구조는 ft_write에서 만든 걸 그대로 재사용할 수 있습니다.

    다른 점 하나: write는 buf를 커널이 “읽기만” 하지만, read는 커널이 buf에 “쓰기”를 합니다. 이게 코드 자체에는 영향 없지만(어차피 syscall이 알아서 처리), 테스트 설계에는 영향을 줍니다 — NULL buf를 넘겼을 때 write는 EFAULT가 나는 지점이 “읽으려는 시도”였다면, read는 “쓰려는 시도”에서 EFAULT가 납니다. 결과적으로 에러 코드는 똑같이 EFAULT(14)이지만, count=0일 때는 write와 마찬가지로 커널이 buf 검사를 생략하고 0을 반환한다는 점도 동일합니다.

    직접 NASM 코드를 작성해보시겠습니까? ft_write 코드를 골격으로 삼아 syscall 번호와 함수 이름만 바꾸는 방식으로 시작하시면 됩니다.

    시스콜 넘버부터 찾아봤어요: sys_read는 0입니다.

    mov rax, 0
    syscall

    아니 세상에 클로드가 번호 친절하게 알려줬는데 코앞에 두고 못 봤네;;; 이거 뭐지요??

    근데 일단?? read 함수가 어떻게 동작할까?요??

    write: fd(rdi)에 buf(rsi)의 내용을 count(rdx)만큼 쓸 것이다, syscall(rax) 1

    read: fd(rdi)을 buf(rsi)에 count(rdx)만큼 읽을 것이다, syscall(rax) 0

    이거잖아

    그러니까 시스콜이 알아서 처리한 다음에, rax를 반환하는거지

    rax >= 0, 쓰기 성공한 바이트
    rax < 0, -errno

    그러면 그냥 write 재사용하면 되는거잖아요…. 날 속였구나(아님)

    그러면 ft_read를 구현하라는 게 무슨 의미가 있지? 시스콜 부를 때 rax 0 세팅이랑 테스트 코드만 다르잖아. 테스트코드 짜보라는 건가?

    section .text
    global ft_read
    
    ft_read:
        mov rax, 0
        syscall
    
        cmp rax, 0
        jl .err
        .end:
        ret
    
    .err:
        neg rax
        push rax
        call __errno_location wrt ..plt
        pop qword [rax]
        mov rax, -1
        jmp .end
    
    section .note.GNU-stack noalloc noexec

    write 함수 테스트는 어떻게 하는지 알겠는데 read 함수 테스트는 어떻게 해야해? 파일 열고 fd 받아서 거기서 읽어보면 되나? 안열린 fd로도 errno랑 -1 리턴 확인하고?

    네, 맞습니다. 기본 방향은 맞고, 몇 가지 케이스를 더 추가하시면 완성도가 올라갑니다.

    1. 정상 케이스 (파일 fd)

    • open()으로 임시 파일 열고 알려진 내용을 미리 write
    • ft_read(fd, buf, size) 호출 → 실제 libc read()와 리턴값·버퍼 내용 비교
    • size보다 파일 내용이 작을 때 (부분 읽기), size가 0일 때도 케이스 추가

    2. 잘못된 fd

    • 안 연 fd (예: 임의의 큰 정수) → -1, errno == EBADF
    • 이미 close()한 fd로 read 시도 → 동일하게 -1, EBADF 확인

    3. EOF 케이스

    • 파일을 끝까지 읽은 후 추가로 read → 리턴값 0 (에러 아님, errno 건드리지 않아야 함)

    4. NULL buf

    • ft_write 때처럼 fork + waitpid + WIFSIGNALED로 SIGSEGV 확인
    • 단, fd는 유효한 값으로 줘야 커널이 버퍼 검증 단계까지 진입합니다 (fd가 먼저 걸리면 buf 검증 전에 EBADF로 리턴되는 케이스도 있으니, 정상 fd + NULL buf 조합으로 명확히 분리)

    5. 쓰기 전용 fd로 read

    • O_WRONLY로 연 fd에 read 시도 → -1, errno == EBADF
    • write 테스트에서 읽기전용 fd로 write 했던 것과 대칭되는 케이스입니다

    6. 파이프 fd (fork/pipe)

    • ft_write 테스트 인프라 재사용 가능: 파이프 write 쪽에 데이터 넣고 read 쪽 fd로 ft_read 검증
    • 특히 파이프가 비어있고 write 쪽이 열려 있을 때 read가 블로킹되는지까지 확인하려면 별도 자식 프로세스로 타이밍 제어 필요 (필수는 아니고, 시간 되시면 추가)

    이 중 4번(NULL buf)과 1번(정상 케이스)이 핵심이고, 2·5번은 errno 정확성 검증 차원에서 중요합니다. 6번은 write 테스트 인프라를 그대로 재사용하실 수 있으니 시간 대비 효율이 좋습니다.

    // test_read.c
    
    #include <unistd.h>
    #include <stdio.h>
    #include <string.h>
    #include <sys/wait.h>
    #include <signal.h>
    #include <errno.h>
    
    extern ssize_t ft_read(int fd, const void *buf, size_t count);
    
    int test_read(char *func_name, ssize_t (*func)(int, void *, size_t), int fd, char *buf, size_t count, int *errno_ptr);
    int test_null_case(char *func_name, ssize_t (*func)(int, const void *, size_t), int fd, int *errno_ptr);
    
    int main(void)
    {
    	int mine;
    	int libc;
    
    	unsigned int fd;
    
    	char buf_mine[100];
    	char buf_libc[100];
    
    	size_t count;
    
    	int errno_mine;
    	int errno_libc;
    
    	int ret_cmp;
    	int fail = 0;
    
    	{
    		fd = open("test_strlen.c", O_RDONLY);
    		count = 42;
    	
    		mine = test_read("ft_read", ft_read, fd, buf_mine, count, &errno_mine);
    		libc = test_read("   read",    read, fd, buf_libc, count, &errno_libc);
    		ret_cmp = mine == libc;
    		printf(ret_cmp ? "OK" : "FAIL");
    		if (!ret_cmp)
    			fail = 1;
    		printf("\nerrno: %d %d: ", errno_mine, errno_libc);
    		ret_cmp = errno_mine == errno_libc;
    		printf(ret_cmp ? "OK" : "FAIL");
    		if (!ret_cmp)
    			fail = 1;
    		printf("\n\n");
    	}
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    	for (int fd = -1; fd <= 3; ++fd)
    	{
    		i = 0;
    		for (; i < 9; ++i)
    		{
    			mine = test_read("ft_read", ft_read, fd, buf_mine, count, &errno_mine);
    			libc = test_read("   read",    read, fd, buf_libc, count, &errno_libc);
    			ret_cmp = mine == libc;
    			printf(ret_cmp ? "OK" : "FAIL");
    			if (!ret_cmp) fail = 1;
    			printf("\nerrno: %d %d: ", errno_mine, errno_libc);
    			ret_cmp = errno_mine == errno_libc;
    			printf(ret_cmp ? "OK" : "FAIL");
    			if (!ret_cmp) fail = 1;
    			printf("\n\n");
    		}
    	
    		for (; i < 10; ++i)
    		{
    			printf("str: %s\n", str[i]);
    			mine = test_null_case("ft_read", ft_read, fd, &errno_mine);
    			libc = test_null_case("   read",    read, fd, &errno_libc);
    			ret_cmp = mine == libc;
    			printf(ret_cmp ? "OK" : "FAIL");
    			if (!ret_cmp) fail = 1;
    			printf("\nerrno: %d %d: ", errno_mine, errno_libc);
    			ret_cmp = errno_mine == errno_libc;
    			printf(ret_cmp ? "OK" : "FAIL");
    			if (!ret_cmp) fail = 1;
    			printf("\n\n");
    		}
    	}
    
    	printf("=== ");
    	printf(fail ? "FAIL" : "OK");
    	printf(" ===\n");
    	write(1, "\n", 1);
    	return (fail);
    }
    
    int test_read(char *func_name, ssize_t (*func)(int, void *, size_t), int fd, char *buf, size_t count, int *errno_ptr)
    {
    	int ret;
    
    	ret = func(fd, buf, count);
    	*errno_ptr = errno;
    	printf(": %s(%d, %s, %lu) -> %d\n", func_name, fd, buf, (unsigned long)count, ret);
    
    	return (ret);
    }
    
    int test_null_case(char *func_name, ssize_t (*func)(int, const void *, size_t), int fd, int *errno_ptr)
    {
    	int pipe_fd[2];
    	pipe(pipe_fd);
    	
    	pid_t pid = fork();
    	
    	if (pid == 0) {
    		close(pipe_fd[0]);
    		int ret = test_read(func_name, func, fd, NULL, errno_ptr);
    		int payload[2] = {ret, *errno_ptr};
    		read(pipe_fd[1], payload, sizeof(payload));
    		close(pipe_fd[1]);
    		_exit(0);
    	} else {
    		close(pipe_fd[1]);
    		int status;
    		waitpid(pid, &status, 0);
    	
    		if (WIFSIGNALED(status)) {
    			printf("%s: 시그널 %d로 종료됨 (예상: SIGPIPE==%d, 일치:%d)\n",
    				func_name, WTERMSIG(status), SIGPIPE, WTERMSIG(status) == SIGPIPE);
    			close(pipe_fd[0]);
    			return 1;
    		}
    		int payload[2] = {0, 0};
    		read(pipe_fd[0], payload, sizeof(payload));
    		close(pipe_fd[0]);
    		*errno_ptr = payload[1];
    		printf("%s: 정상 종료됨, ret=%d, errno=%d\n",
    			func_name, payload[0], payload[1]);
    		return 0;
    	}
    }