함수의 전방 선언 개념 Forward Declaration C언어는 코드의 가장 위부터 읽으며 함수를 등록한다 가독성을 위해 main()을 아래로 내리고, 그 위에 함수를 선언하는 것 을 말한다 중요성 호출 순서에 따라 발생되는 문제 해결 가능 여러 소스코드를 불러올 때, 중복되는 함수 호출 해결 가능 사용 예제 .c vs .h static & extern 기본 개념 파일간 함수는 기본적으로 공유 되지만 변수는 공유되지 않는다 extern 전역 변수 다른 소스코드에서 참조 가능 함수는 기본적으로 적용되어 있는것 static 함수 내 지역 변수 앞에 사용 시 전역 변수 가 된다 그 외 전역 변수 앞에 사용 시 지역 변수/함수 가 된다
MeetShaxs Software: Understanding the Platform and Choosing Better Collaboration Tools In today’s digital workplace, businesses rely on technology to manage communication, projects, meetings, documents, and daily workflows. Meetshaxs software is a term that has appeared in online discussions describing an all-in-one collaboration solution. It is generally associated with features such as team communication, video meetings, task management, file sharing, and automated meeting summaries. However, before adopting any unfamiliar software, organizations should carefully evaluate its credibility, available features, security, and overall reliability. What Is MeetShaxs Software? MeetShaxs is commonly described online as a collaboration platform designed to bring several workplace functions together. The concept behind such software is straightforward: instead of requiring employees to switch between different applications for meetings, messaging, tasks, and documents, one centralized platform can potentially handle these activities. This approach can be attractive to businesses because excessive application switching can interrupt concentration and make workflows more complicated. Employees may spend significant time searching for information, checking notifications, moving between applications, or determining where specific tasks and conversations belong. However, publicly verifiable information about MeetShaxs is limited. There does not appear to be a clearly established official vendor presence, public pricing information, major software directory profile, or widely recognized independent review history. This makes verification particularly important for anyone considering the software. Why Software Verification Matters Businesses should never select software based solely on articles or online descriptions. A legitimate software product normally has several verification signals, including an official website, documentation, customer support information, pricing details, security information, and independent reviews. Checking software directories can also help determine whether a platform has an established customer base. App marketplaces are another useful source because legitimate mobile applications generally have identifiable developers, version histories, user reviews, and update information. Security should be an equally important consideration. Collaboration platforms may handle sensitive business conversations, customer information, documents, and employee data. Before giving an unfamiliar platform access to company information, organizations should investigate its privacy policies, security practices, data-processing terms, and compliance documentation. The Problem of Workplace Tool Sprawl One of the main reasons an all-in-one collaboration platform sounds appealing is the growing problem of tool sprawl. Modern businesses often use separate applications for instant messaging, video conferencing, project management, document storage, customer communication, and knowledge management. Although individual applications can be useful, too many tools can create unnecessary complexity. Employees may receive notifications from multiple services while important information becomes scattered across different platforms. Research referenced by the source material shows that knowledge workers can spend a substantial portion of their working time coordinating activities rather than concentrating on skilled work. Frequent switching between applications can further increase the time required to regain focus. Therefore, the underlying problem that MeetShaxs is described as addressing is genuine, even when the software itself requires additional verification. How to Reduce Application Overload Businesses do not necessarily need to purchase another platform to solve tool sprawl. The first step should be an internal software audit. Companies can create a list of every application employees use and identify overlapping functions. For example, if three different applications are being used for team conversations, the organization can determine whether one platform can handle the majority of communication needs. The same process can be applied to project management, document sharing, meetings, and task tracking. Companies should also establish clear rules about where information belongs. Project decisions can remain in a project management system, quick conversations can happen through team messaging, and permanent reference material can be stored in a knowledge base. Clear boundaries reduce confusion and make information easier to locate. Established Alternatives to Consider Businesses looking for collaboration tools should compare verified platforms according to their specific requirements. Asana and ClickUp can be useful for project and workflow management, while Slack and Microsoft Teams are widely used for team communication. Notion can support knowledge management, documentation, and lightweight project organization. Zoom is another established option for organizations that primarily need reliable video conferencing. The best choice depends on the company’s workflow rather than the number of features advertised. A platform with fewer features but strong reliability, documentation, security, and support can be more valuable than an unfamiliar service promising to do everything. Making a Better Software Decision Before adopting any new collaboration software, businesses should test whether it actually solves a measurable problem. Teams can identify their biggest productivity obstacles, compare several verified platforms, and conduct a controlled trial before making a long-term commitment. It is also important to remember that consolidation is not always the perfect answer. Specialized teams may need specialized applications for design, development, marketing, or customer management. The goal should not simply be to reduce the number of applications. Instead, businesses should eliminate unnecessary duplication while keeping tools that provide genuine value. Conclusion MeetShaxs software represents the broader idea of bringing workplace communication and productivity features into one environment. While this concept can address real challenges caused by excessive applications and fragmented workflows, users should verify any unfamiliar software before trusting it with business information. A careful software audit, clear workflow policies, security checks, and comparison with established platforms can help organizations make smarter technology decisions. Rather than choosing a product because it sounds convenient, businesses should focus on evidence, reliability, security, and the specific problems they need their software to solve.
교재 코드를 그대로 넣었을 때 56점이었던 malloc을 84점까지 올린 과정이다. 그런데 이 1편에서 제일 중요한 건 점수가 아니라, footer를 없애는 "최적화"를 했는데 점수가 그대로였던 일 이다. 그 실패 덕분에 트레이스를 한 줄씩 열어보기 시작했다. 점수는 어떻게 매겨지나 기본 코드 /* * mm-naive.c - The fastest, least memory-efficient malloc package. * * In this naive approach, a block is allocated by simply incrementing * the brk pointer. A block is pure payload. There are no headers or * footers. Blocks are never coalesced or reused. Realloc is * implemented directly using mm_malloc and mm_free. * * NOTE TO STUDENTS: Replace this header comment with your own header * comment that gives a high level description of your solution. */ #include <stdio.h> #include <stdlib.h> #include <assert.h> #include <unistd.h> #include <string.h> #include "mm.h" #include "memlib.h" /********************************************************* * NOTE TO STUDENTS: Before you do anything else, please * provide your team information in the following struct. ********************************************************/ team_t team = { /* Team name */ "ateam", /* First member's full name */ "Harry Bovik", /* First member's email address */ "bovik@cs.cmu.edu", /* Second member's full name (leave blank if none) */ "", /* Second member's email address (leave blank if none) */ ""}; /* single word (4) or double word (8) alignment */ #define ALIGNMENT 8 /* rounds up to the nearest multiple of ALIGNMENT */ #define ALIGN(size) (((size) + (ALIGNMENT - 1)) & ~0x7) #define SIZE_T_SIZE (ALIGN(sizeof(size_t))) /* * mm_init - initialize the malloc package. */ int mm_init(void) { return 0; } /* * mm_malloc - Allocate a block by incrementing the brk pointer. * Always allocate a block whose size is a multiple of the alignment. */ void *mm_malloc(size_t size) { int newsize = ALIGN(size + SIZE_T_SIZE); void *p = mem_sbrk(newsize); if (p == (void *)-1) return NULL; else { *(size_t *)p = size; return (void *)((char *)p + SIZE_T_SIZE); } } /* * mm_free - Freeing a block does nothing. */ void mm_free(void *ptr) { } /* * mm_realloc - Implemented simply in terms of mm_malloc and mm_free */ void *mm_realloc(void *ptr, size_t size) { void *oldptr = ptr; void *newptr; size_t copySize; newptr = mm_malloc(size); if (newptr == NULL) return NULL; copySize = *(size_t *)((char *)oldptr - SIZE_T_SIZE); if (size < copySize) copySize = size; memcpy(newptr, oldptr, copySize); mm_free(oldptr); return newptr; } malloc lab은 mm_malloc , mm_free , mm_realloc 을 직접 구현하고, mdriver 가 트레이스 파일 11개를 돌려서 점수를 매긴다. 트레이스 한 줄이 요청 하나다. a 0 2040 ← malloc(2040), id 0 f 0 ← free(id 0) r 0 4097 ← realloc(id 0, 4097) 점수는 두 개로 나뉜다. util (60점) : 살아 있는 데이터가 가장 많았을 때의 크기 ÷ 힙이 가장 컸을 때의 크기. 트레이스 11개의 평균. thru (40점) : 초당 처리한 요청 수(Kops). 600 Kops를 넘으면 만점. 점수 = 60 × util + 40 × min(1, Kops / 600) 즉 이 lab에서는 처리 속도보다 메모리를 효율적으로 쓰는 게 중요하다. 속도는 600 Kops만 넘기면 만점이라, 그다음부터는 메모리를 얼마나 알뜰하게 쓰느냐가 점수를 가른다. 측정 환경: x86-64 / gcc 13.3.0 / make 기본 -O2 0. 교재 코드: 복붙하지 않고 다시 쓰기 CS:APP 9.9절에는 implicit free list로 만든 할당기 코드가 통째로 나온다. 헤더와 footer로 블록을 관리하고, first fit으로 빈 블록을 찾고, free할 때 바로 앞뒤 블록과 합친다. 그런데 나는 이 코드를 그래도 복붙하지 않고 교재 코드를 충분히 이해한 뒤 최대한 보지 않고 직접 짜봤다. 이 lab은 결국 PACK , GET , PUT , HDRP , FTRP , NEXT_BLKP , PREV_BLKP 같은 매크로로 메모리를 4바이트씩 직접 읽고 쓰는 게 전부다. 이게 손에 안 익으면 직접 코드를 짤 때 많이 해맬 것 같아서 설계가 되어있는 교재 코드를 어차피 옮겨 쓰고 시작해볼 걸 그냥 직접 옮겨보자 싶었다. 그래서 순서를 이렇게 잡았다. 교재 코드를 읽으면서 각 함수가 뭘 하는지, 매크로가 어떤 주소를 계산하는지 이해한다. 매크로만 정의해두고 교재를 덮는다. 함수 몸통은 교재를 최대한 안 보고 직접 채운다. 이렇게 하니까 "bp에서 4바이트 앞이 헤더", "헤더에 적힌 크기만큼 가면 다음 블록", "앞 블록은 내 바로 앞 8바이트 자리의 footer를 읽어서 찾는다" 같은 블록 단위 계산이 손에 익어서 다음 최적화들을 할 때 훨신 수월했던 것 같다. 대신 first fit을 구현할 때 빼먹거나 다르게 쓴 게 좀 있어서 에러가 좀 나긴 했다... 1. first fit: 내가 빼먹었던 것들 처음 채운 핵심 두 함수는 이랬다. 지금 보니까 엄청 간단하게 아무생각 없이 쓴 거 같다. 디버깅을 하지 않아도 되는 코드를 짜는 게 중요한데 다음부터는 처음 짤 때부터 꼼꼼하게 설계하고 코드를 짜야할 것 같다. 단계마다 실제로 짰던 코드를 그대로 보여주고, 왜 터졌는지 적어보겠다. 헛다리 1: 블록을 아예 안 자르기 처음엔 split_block 같은 건 없었다. find_fit 이 블록을 찾으면 place 가 헤더와 footer에 요청 크기를 쓰는 게 전부였다. static void *find_fit(size_t asize) { ... if(GET_SIZE(HDRP(current)) == asize && !GET_ALLOC(HDRP(current))){ return current; // 크기가 "딱 맞는" 블록만 찾음 } ... } static void place(void *bp, size_t asize) { PUT(HDRP(bp), PACK(asize, 1)); // 요청 크기만 쓰고 끝 PUT(FTRP(bp), PACK(asize, 1)); } find_fit 은 딱 맞는 블록이 없으면 NULL 을 돌려주고, 그러면 extend_heap 으로 힙을 늘린 다음 그 블록에 place 를 한다. 문제는 늘린 블록이 요청보다 훨씬 크다는 거다. 그런데 place 는 앞부분에 요청 크기만 써버린다. 돌리자마자 segfault가 났다. gdb로 잡아보면 extend_heap 이 부른 coalesce 안에서 터진다. Program received signal SIGSEGV, Segmentation fault. 0x0000555555556f73 in coalesce (bp=0x7ffff68d2020) at mm.c:167 167 size_t prev_alloc = GET_ALLOC(FTRP(PREV_BLKP(bp))); #1 extend_heap (words=1024) at mm.c:112 #2 mm_malloc (size=4080) at mm.c:142 (gdb) p/x *(unsigned int*)((char*)bp - 8) ← 앞 블록 footer 자리 $1 = 0x1ff0 새로 늘린 청크( bp )의 바로 앞 4바이트는 원래 앞 블록의 footer여야 한다. 그런데 거기서 읽은 값으로 PREV_BLKP 를 계산하니까 엉뚱한 곳으로 가서 터진 거다. 왜 footer가 망가졌는지 보려고 malloc(2040) 한 번만 부르고 힙을 직접 읽어봤다. a header: size=2048 alloc=1 NEXT_BLKP(a) header: 0x0 (size=0 alloc=0) heap size = 8208 malloc(2040) 은 블록 크기 2048이 필요하다. mm_init이 만든 free 블록은 4096이라 "딱 맞는" 게 아니니까 find_fit 이 못 찾는다. 그래서 힙을 4096 늘리고, 그게 앞의 4096이랑 합쳐져서 8192짜리 블록이 된다. 그런데 place 가 앞에 2048이라고만 써버려서, 뒤 6144바이트는 헤더도 footer도 없는 공간 이 됐다. 그 자리 첫 4바이트가 0이라서 힙 순회 입장에서는 "크기 0 = 에필로그"처럼 보이고, 힙이 거기서 끝난 것처럼 된다. 이런 블록들이 쌓이다가 결국 쓰레기 footer를 읽은 거다. 여기서 처음으로 "블록 경계는 헤더랑 footer에 쓴 숫자가 전부"라는 게 실감 났다. 크기를 안 써주면 그 공간은 존재하지 않는 거나 마찬가지다. 헛다리 2: 자르긴 하는데, 늘린 블록만 남는 부분에도 헤더와 footer를 써줘야 한다는 걸 알고 split_block 을 만들었다. 처음 버전은 이랬다. static void *split_block(void *bp, size_t size){ size_t total = GET_SIZE(HDRP(bp)); // if(total <= 24){ // return bp; // } PUT(HDRP(bp), PACK(size, 0)); // 앞: 요청 크기 PUT(FTRP(bp), PACK(size, 0)); PUT(HDRP(NEXT_BLKP(bp)), PACK(total-size, 0)); // 뒤: 남는 크기 PUT(FTRP(NEXT_BLKP(bp)), PACK(total-size, 0)); return bp; } 그리고 이걸 extend_heap 으로 늘린 경우에만 불렀다. " find_fit 은 어차피 딱 맞는 블록만 찾으니까 자를 필요가 없고, 늘린 블록만 크니까 그것만 자르면 되겠지"라고 생각했다. else{ extend = MAX(size, CHUNKSIZE); if((bp = extend_heap(extend / WSIZE)) == NULL){ return NULL; } if(GET_SIZE(HDRP(bp)) != extend){ // 앞 free랑 합쳐져서 커졌으면 bp = split_block(bp, size); // 그때만 자른다 } place(bp, size); return bp; } segfault는 사라졌는데 이번엔 메모리가 바닥났다. ERROR: mem_sbrk failed. Ran out of memory... ERROR [trace 4, line 7671]: mm_malloc failed. split_block 으로 잘라낸 뒷부분은 이제 제대로 된 free 블록이다. 그런데 find_fit 이 여전히 크기가 딱 맞는 블록만 찾고 있었다. 예를 들어 첫 malloc(2040) 뒤에는 6144짜리 free 블록이 생기는데, 다음 요청이 그보다 작아도 크기가 딱 6144가 아니면 거기 못 들어간다. 그러니까 요청이 올 때마다 힙을 새로 늘렸고, 결국 바닥났다. 헛다리 3: 남는 게 0바이트여도 자르기 find_fit 을 >= 로 바꾸고, 찾은 블록이든 늘린 블록이든 항상 split_block 을 부르게 바꿨다. 그런데 이번엔 위 split_block 에서 주석 처리해둔 조건이 문제가 됐다. 블록 크기가 요청이랑 딱 같으면 total - size 가 0이다. 그러면 split_block 은 다음 블록 자리에 크기 0짜리 헤더를 쓴다. 크기 0이면 에필로그랑 구분이 안 된다. 게다가 FTRP(NEXT_BLKP(bp)) 는 "다음 블록 + 0 − 8"이라 자기 자신의 footer 자리 를 가리킨다. 방금 쓴 footer를 0으로 덮어써버리는 거다. 그래서 남는 크기가 최소 블록(헤더 4 + footer 4 + 최소 payload 8 = 16바이트)보다 작으면 자르지 않는 조건을 넣었다. 그리고 안 잘린 블록이 들어오면 place 가 블록 전체 크기를 쓰게 바꿨다. 최종 코드 교재코드 + first fit /* * mm-naive.c - The fastest, least memory-efficient malloc package. * * In this naive approach, a block is allocated by simply incrementing * the brk pointer. A block is pure payload. There are no headers or * footers. Blocks are never coalesced or reused. Realloc is * implemented directly using mm_malloc and mm_free. * * NOTE TO STUDENTS: Replace this header comment with your own header * comment that gives a high level description of your solution. */ #include <stdio.h> #include <stdlib.h> #include <assert.h> #include <unistd.h> #include <string.h> #include "mm.h" #include "memlib.h" /********************************************************* * NOTE TO STUDENTS: Before you do anything else, please * provide your team information in the following struct. ********************************************************/ team_t team = { /* Team name */ "Team 10", /* First member's full name */ "Shin Ye Na", /* First member's email address */ "syn77@pusan.ac.kr", /* Second member's full name (leave blank if none) */ "", /* Second member's email address (leave blank if none) */ ""}; /* single word (4) or double word (8) alignment */ #define ALIGNMENT 8 #define WSIZE 4 #define DSIZE 8 #define CHUNKSIZE (1<<12) // 페이지 단위 할당 #define MAX(x, y) ((x) > (y) ? (x) : (y)) #define PACK(size, alloc) ((size) | (alloc)) #define GET(p) (*(unsigned int *)(p)) // int로 읽기 #define PUT(p, val) (*(unsigned int *)(p) = (val)) #define GET_SIZE(p) (GET(p) & ~0x7) #define GET_ALLOC(p) (GET(p) & 0x1) #define HDRP(bp) ((char *)(bp) - WSIZE) #define FTRP(bp) ((char *)(bp) + GET_SIZE(HDRP(bp)) - DSIZE) #define NEXT_BLKP(bp) ((char *)(bp) + GET_SIZE(HDRP(bp))) #define PREV_BLKP(bp) ((char *)(bp) - GET_SIZE((char *)(bp) - DSIZE)) /* rounds up to the nearest multiple of ALIGNMENT */ #define ALIGN(size) (((size) + (ALIGNMENT - 1)) & ~0x7) // 정렬의 배수로 올림 (~0x7로 지움 구현) #define SIZE_T_SIZE (ALIGN(sizeof(size_t))) // size_t를 올림 -> 여기서는 8바이트 즉, 푸터와 헤더 8바이트를 더해서 배수 구함 static void *extend_heap(size_t words); // 함수 프로토타입 선언 static void *coalesce(void *bp); static void *find_fit(size_t asize); static void place(void *bp, size_t asize); static void *split_block(void *bp, size_t size); static char *heap_listp; // 전역변수로 포인터 선언 /* * mm_init - initialize the malloc package. */ int mm_init(void) { if((heap_listp = mem_sbrk(4*WSIZE)) == (void *)-1){ return -1; } PUT(heap_listp, 0); PUT(heap_listp + WSIZE, PACK(DSIZE, 1)); // Prologue header PUT(heap_listp + 2 * WSIZE, PACK(DSIZE, 1)); // Prologue footer PUT(heap_listp + 3 * WSIZE, PACK(0, 1)); // Epilogue header heap_listp += 4 *WSIZE; // 첫 청크부터 시작하는 작은 최적화(처음에는 heap의 끝을 가르킴 ) if(extend_heap(CHUNKSIZE / WSIZE) == NULL){ // heap 공간 확보 return -1; } return 0; // 정상종료 } static void *extend_heap(size_t words) { size_t size; char *bp; size = (words%2) ? (words+1) * WSIZE : words * WSIZE; if((bp = mem_sbrk(size)) == (void *)-1){ return NULL; } PUT((char *)bp - WSIZE, PACK(size, 0)); // new chunk header PUT(FTRP(bp), PACK(size, 0)); // newe chunk footer PUT(HDRP(NEXT_BLKP(bp)), PACK(0,1)); // epilogue return coalesce(bp); } /* * mm_malloc - Allocate a block by incrementing the brk pointer. * Always allocate a block whose
여러분은 AI를 어떻게 사용하고 계신가요? 요즘 AI를 어떻게 써야 할지 자주 고민한다. 개발자에게 필요한 AI 활용 능력이 정확히 뭔지 아직 잘 모르겠다. ChatGPT나 Claude에게 코드를 짜 달라고 하는 게 전부는 아닐 텐데, 다른 개발자들은 실무에서 AI를 어떻게 쓰는지, AI 답변을 정말 믿고 쓰는지, 못 믿는다면 뭘로 확인하는지 궁금했다. 그러다 Stack Overflow의 2026 Developer Survey를 읽게 됐다. 올해는 169개국에서 3만여 명이 참여했고 AI부터 업무 환경, 지식과 커뮤니티까지 꽤 넓게 다룬다. 그중 AI 쪽 결과 몇 개가 계속 머리에 남았다. AI를 정말 신뢰하고 있을까? 설문은 AI 도구의 결과물을 얼마나 신뢰하는지 물었다. 가장 많이 나온 답은 의외로 명확했다. 출력 결과를 쉽게 확인할 수 있을 때 신뢰한다. 응답자의 48.0%가 해당 답을 골랐다. "중요한 업무 결정을 포함해 여러 일에서 AI를 신뢰한다"는 응답은 6.6%뿐이었다. 경력 1~5년 차만 보면 4.5%까지 내려간다. 믿느냐 안 믿느냐로 딱 나뉘는 게 아니었다. 확인할 수 있는 일에는 적극적으로 쓰지만 판단이 어려운 중요한 결정까지 맡기지는 않는다. AI를 쓰긴 쓰되 사람이 판단할 자리는 남겨 두는 셈이다. AI의 답변에도 출처가 필요하다 출처에 관한 질문도 있었다. AI가 내놓은 기술적인 답변을 믿으려면 출처 표기가 얼마나 중요하냐는 질문에 79%가 "중요하다" 또는 "매우 중요하다"고 답했다. 매우 중요하다가 52.1%, 중요하다가 26.5%였다. 당연한 결과다. 인터넷에서 뭔가를 찾을 때도 우리는 어디서 나온 정보인지, 누가 썼는지, 지금도 맞는 정보인지부터 본다. 그런데 코드는 사정이 좀 다르다. AI가 공식 문서를 참고해서 답할 수는 있어도 그 코드가 우리 프로젝트에서 맞는지는 출처만 봐서는 알 수 없다. 결국 내가 코드를 이해해야 한다. 왜 이 자료구조를 썼는지, 왜 이 API를 골랐는지, 이 방식의 단점은 뭔지 알아야 하고 기존 코드와 부딪히지 않는지, 성능이나 안정성에 문제는 없는지도 따져 봐야 한다. AI가 코드를 짤수록 기본기가 중요해지는 이유 AI가 코드를 잘 짤수록 직접 타이핑하는 시간은 줄어든다. 그렇다고 개발자에게 필요한 능력까지 줄어든다고 보지는 않는다. AI가 낸 결과를 확인하려면 그 결과를 이해할 수 있어야 하기 때문이다. 자료구조와 알고리즘, 네트워크, 데이터베이스, 운영체제 같은 CS 지식이 필요하고 코드를 읽고 문제를 재현하며 원인을 좁혀 가는 능력도 필요하다. 그중에서도 여러 방법 가운데 뭐가 맞는지 고르는 일이 제일 어렵다. AI가 세 가지 방법을 내놨는데 셋 다 잘 돌아가고 테스트도 통과한다고 해 보자. 성능을 볼지, 유지보수를 볼지, 기존 시스템과 잘 맞는지를 볼지는 결국 사람이 정해야 한다. 이건 코드를 짤 줄 아느냐와는 다른 문제다. 그래서 요즘 말하는 기본기는 문법을 많이 아는 것보다 AI가 만든 결과를 읽고 의심하고 확인해서 고르는 힘에 더 가깝다고 생각한다. 문제는 늘 어떤 도메인 안에서 생긴다 AI에게 이렇게 부탁한다고 해 보자. "쇼핑몰 주문 취소 API를 만들어 줘." AI는 꽤 그럴듯한 코드를 금방 내놓는다. 하지만 실제 서비스라면 알아야 할 게 훨씬 많다. 주문 취소는 언제까지 되는지, 이미 배송이 시작된 주문은 어떻게 하는지, 결제 금액은 어떻게 환불하는지, 쓴 쿠폰과 포인트는 어떻게 돌려주는지, 판매자 쪽에는 무슨 처리가 필요한지 같은 것들이다. 이건 코드가 아니라 업무의 문제다. 코드를 아무리 잘 짜도 뭐가 맞는 결과인지는 그 서비스의 규칙과 목적을 알아야 판단할 수 있다. 같은 "회원 탈퇴" 기능이라도 금융 서비스와 커뮤니티 서비스에서 챙겨야 할 게 전혀 다른 것처럼, 어떤 문제를 풀든 그 문제가 속한 도메인을 알아야 제대로 풀 수 있다. 설문에서도 비슷한 이야기가 나온다. AI가 일을 제대로 하려면 어떤 맥락이 필요하냐는 질문에 응답자들은 프로젝트 목표와 요구사항을 가장 많이 골랐다. 맥락 응답 비율 프로젝트 목표와 요구사항 85.2% 코드, 저장소, 기술 문서 73.3% 이전 의사결정과 그 이유 44.0% 비즈니스 목표나 제품 전략 33.0% 결국 좋은 답을 얻으려면 프롬프트를 잘 쓰는 것만으로는 모자란다. AI에게 무엇을 알려 줘야 하는지 알아야 하고 그건 업무 맥락과 도메인 지식에서 나온다. 구현은 AI가 금방 해 주는 시대라 무엇을 만들어야 하는지, 왜 그게 맞는지 답할 수 있는 사람이 더 귀해지지 않을까? 그런데 그 맥락, 충분히 주어지고 있을까? 업무에 필요한 맥락을 얻는 데 뭐가 방해되느냐는 질문에 63.2%가 "정보가 불완전하다"를 골랐다. "중요한 맥락이 사람들 머릿속에만 있다"가 60.9%, "정보가 오래됐다"가 56.5%로 뒤를 이었다. 일을 시작했거나 다 끝낸 뒤에야 중요한 맥락을 알게 되는 경우가 얼마나 되느냐는 질문은 더 뜨끔했다. "매우 자주", "자주", "가끔"을 합치면 약 79%였다. 이 숫자를 보고 문서화를 다시 생각하게 됐다. 여기서 말하는 "정보가 불완전하다."는 "문서는 있는데 정작 필요한 부분이 빠져있다."는 뜻이지 않을까? AI가 줄여 준 과정은 어디에 남을까? 예전에는 문제 하나를 풀 때 이래와 같은 과정을 거쳤다. 문제 발생 → 검색 → 해결책 비교 → 적용 → 실패 → 원인 분석 → 재시도 → 해결 느리지만 그 과정에서 뭘 골랐고 왜 그랬는지가 자연스럽게 머리에 남는다. AI를 쓰면 이 과정이 확 짧아진다. 문제를 설명하고 코드를 받아 돌려 보고 고치면 끝이다. 생산성만 보면 분명 좋은 변화다. 그런데 "왜 이렇게 풀었는가"는 어디에 남을까? AI가 준 코드를 그대로 썼다면 몇 달 뒤 누군가 그 코드를 보며 이렇게 물을 수 있다. "왜 이렇게 구현했지?" "다른 방법은 검토 안 했나?" "이 제약 때문에 이렇게 한 건가?" 맥락이 남아 있지 않으면 코드는 남아도 판단은 사라진다. 프로젝트를 하면서 기술을 고른 이유나 AI 제안을 반영하지 않은 이유를 적어 두긴 했다. 이번 설문을 읽고 나서는 이걸 좀 더 신경 써서 남겨야겠다고 마음먹은 것 같다. 거창한 문서까지는 필요 없다. 내가 푼 문제를 누군가 이어받는다고 생각하고 쓰면 충분하지 않을까. 결국 AI 활용 능력이란 무엇일까? 설문을 읽기 전에는 AI 활용 능력을 꽤 단순하게 생각했다. 프롬프트를 잘 작성하는 것. 상황에 맞는 스킬을 활용하거나 필요하다면 직접 만들어 사용하는 것. 업무에 필요한 코드를 빠르게 만들어내는 것. 반복적인 작업을 자동화하는 것. 물론 이것도 중요한 능력이다. 그런데 결과를 하나씩 보며 생각이 달라짐을 느꼈다. 응답자의 62%가 업무에서 AI 도구를 쓰는 데 긍정적일 만큼 AI는 이미 개발자의 일상에 깊이 들어와 있다. 그런데도 AI 결과를 중요한 결정에 그대로 쓰는 사람은 많지 않았다. 확인할 수 있는 결과를 믿고 출처를 따지고 요구사항이나 코드, 문서 같은 맥락을 챙겼다. AI를 잘 쓰는 것만으로는 부족하다. 결과를 확인하려면 기본기가 있어야 하고 맞는 결과를 만들려면 업무 맥락과 도메인 지식이 있어야 한다. AI로 빨리 푼 문제일수록 왜 그렇게 풀었는지 남겨 두는 일도 중요해진다. 요즘 내가 느끼는 개발자의 일은 코드를 짜는 것보다 AI와 함께 문제를 정의하고 판단하는 쪽에 더 가깝다. 그래서, 여러분은 AI를 어떻게 사용하고 계신가요? 처음 질문으로 돌아가 보자. 이번 설문을 읽으면서 AI 활용 능력이 AI에게 질문을 얼마나 잘하느냐로만 갈리지는 않는다 고 느꼈다. AI에게 무엇을 물어볼지 알고 돌아온 답을 확인하는 것. 현재 우리가 진행중인 업무에 맞는지 따져 보고 왜 그렇게 했는지 적어 두는 것. 개발자에게 필요한 AI 활용 능력은 이쪽에 더 가깝지 않을까. 참고 Stack Overflow Developer Survey 2026 AI — Stack Overflow Developer Survey 2026 Knowledge — Stack Overflow Developer Survey 2026 Domain expertise still wanted: the latest trends in AI (Stack Overflow Blog)
1. 단어의 표현이 필요한 이유 단어의 표현(Word Representation)은 문자로 이루어진 단어를 숫자로 변환하는 작업이다. 문서 분류·번역 등을 하려면 확률 계산·덧셈·뺄셈 같은 수학적 연산 이 필요하다. 문자열은 그대로 연산할 수 없으므로 분석 전에 숫자로 바꾼다. 숫자로 바꾸면서 단어 본연의 의미를 어떻게 살릴지가 핵심 과제다. 2. 원핫-인코딩 (One-Hot-Encoding) 단어마다 고유 인덱스를 주고, 그 자리만 1, 나머지는 0인 벡터로 표현하는 방법이다. 원숭이, 바나나, 사과 원숭이, 바나나, 사과, 코끼리 원숭이 = [1, 0, 0] 원숭이 = [1, 0, 0, 0] 바나나 = [0, 1, 0] 바나나 = [0, 1, 0, 0] 사과 = [0, 0, 1] 사과 = [0, 0, 1, 0] 코끼리 = [0, 0, 0, 1] 벡터: 숫자들의 나열 벡터의 차원 수 = 어휘(단어) 수 새로운 단어가 등장하면 차원을 하나 늘리는 직관적인 방법이다. 2-1. 텍스트를 단어 목록으로 바꾸기 import re import numpy as np def tokenize(text): return re.findall(r"[가-힣A-Za-z0-9]+", text.lower()) tokenize("사과, 바나나! 사과?") # ['사과', '바나나', '사과'] tokenize("NLP Model과 DATA 2026") # ['nlp', 'model과', 'data', '2026'] tokenize("원숭이-코끼리_바나나") # ['원숭이', '코끼리', '바나나'] 한글·영문·숫자만 남기고 영문은 소문자로 통일한다. 단, 공백 기준이므로 model과 처럼 조사가 붙은 채 남는다. np.set_printoptions(precision=3, suppress=True) 는 실제 값은 그대로 두고 화면 표시 만 바꾼다. ( 1.234567 → 1.235 로 표시) 2-2. 어휘와 인덱스 만들기 단어를 벡터로 바꾸려면 먼저 고정된 어휘 순서와 인덱스 가 필요하다. words = ["원숭이", "바나나", "사과", "사과", "코끼리"] vocabulary = list(dict.fromkeys(words)) # 중복 제거, 첫 등장 순서 유지 word_to_index = {word: i for i, word in enumerate(vocabulary)} print("어휘:", vocabulary) print("인덱스:", word_to_index) # 어휘: ['원숭이', '바나나', '사과', '코끼리'] # 인덱스: {'원숭이': 0, '바나나': 1, '사과': 2, '코끼리': 3} 중복 제거 방법에 따라 어휘 순서가 달라질 수 있다. order_words = ["원숭이", "바나나", "사과", "바나나"] sorted_vocabulary = sorted(set(order_words)) first_seen_vocabulary = list(dict.fromkeys(order_words)) print("정렬된 어휘:", sorted_vocabulary) print("첫 등장 순서:", first_seen_vocabulary) # 정렬된 어휘: ['바나나', '사과', '원숭이'] # 첫 등장 순서: ['원숭이', '바나나', '사과'] 배열 위치와 바로 연결하려면 0부터 시작하는 인덱스 가 편리하다. 2-3. 원핫 벡터와 UNK 처리 어휘에 없는 단어(미등록 단어)는 정해 둔 <UNK> 위치로 보낸다. vocabulary_with_unk = ["<UNK>", "원숭이", "바나나", "사과"] index_with_unk = {w: i for i, w in enumerate(vocabulary_with_unk)} query_words = ["사과", "코끼리", "바나나"] query_indices = [index_with_unk.get(w, index_with_unk["<UNK>"]) for w in query_words] # [3, 0, 2] identity = np.eye(len(vocabulary_with_unk), dtype=int) query_one_hot = identity[query_indices] # [[0 0 0 1] # [1 0 0 0] # [0 0 1 0]] 단위행렬( np.eye )의 각 행이 곧 원핫 벡터 이므로, 인덱스로 행을 꺼내면 된다. 각 행의 합은 1이고, argmax(axis=1) 로 1의 위치(원래 인덱스)를 복원할 수 있다. -> # [3, 0, 2] # 코끼리 -> 인덱스 0 벡터 [1 0 0 0] # 기린 -> 인덱스 0 벡터 [1 0 0 0] 서로 다른 미등록 단어가 모두 같은 UNK 벡터가 되는 한계가 있다. 2-4. 원핫-인코딩의 한계 1) 차원 크기의 문제 단어의 수만큼 차원이 필요하다. 2017년 표준국어대사전 등재 단어 약 50만 개 → 50만 차원 이 필요하며, 대부분이 0인 희소 벡터 가 된다. 2) 의미를 담지 못하는 문제 원핫 벡터는 서로 직교 하므로 코사인 유사도가 항상 0이다. 원숭이 [0, 1], 바나나 [1, 0] similarity = (0×1) + (1×0) / ... = 0 원숭이·바나나 , 원숭이·사과 , 개·고양이 모두 유사도 0 → 단어 사이의 멀고 가까움을 비교할 수 없다. 3. 단어 임베딩 (Word Embedding) 원핫-인코딩의 한계 결과 차원이 너무 큼 연산 낭비, 모델 학습에 불리 단어 의미를 담지 못함 분석을 효과적으로 수행할 수 없음 단어 임베딩은 단어의 의미를 간직하는 밀집 벡터(Dense Vector)로 표현하는 방법이다. 임베딩은 단어·문장을 벡터로 변환해 벡터 공간에 끼워 넣는다(embed)는 의미다. 원핫 (3차원) 밀집 벡터 (2차원) 원숭이 [0, 1, 0] 원숭이 [0.5, 0.8] 바나나 [1, 0, 0] 바나나 [0.8, 0.1] 사과 [0, 0, 1] 사과 [0.2, 0.2] 구분 희소 벡터 (원핫) 밀집 벡터 (임베딩) 값 대부분 0 각 원소가 값을 가짐 새 단어 추가 차원 증가 차원 유지 연산 낭비가 큼 분류·예측 모델 학습 시 연산 감소 3-1. 밀집 벡터를 만드는 방법: 분포 가설 밀집 벡터로 차원 문제는 해결되지만, 단어를 어디에 위치시켜야 하는지 명확한 방법은 없다. 분포 가설: 같은 문맥에서 등장하는 단어는 유사한 의미를 지닌다. 단어의 의미는 곧 그 언어에서의 활용이다. — 비트겐슈타인 임의의 위치에 벡터를 생성한다. 같은 문맥에 등장하는 단어를 더 가까이 옮긴다. 3-2. 단어 표현 방법 분류 구분 의미 예시 Local Representation (Discrete) 단어 자체만 보고 값을 매핑 One-hot vector, N-gram, BoW, TDM (Count Based) Distributed Representation (Continuous) 주변 단어를 참조해 표현 Word2Vec, FastText (Prediction Based), GloVe, LSA (Count Based) 4. 유사도 계산 (Text Similarity) 4-1. 유클리디안 거리 (Euclidean distance) 두 벡터 사이의 직진 거리를 계산한다. (피타고라스 정리) d(p, q) = √( (q1 - p1)2 + (q2 - p2)2 + ... + (qn - pn)2 ) 4-2. 코사인 유사도 (Cosine Similarity) 두 벡터 사이의 각(방향)을 이용해 유사도를 측정한다. similarity = cos(θ) = (A · B) / (‖A‖ ‖B‖) 각도 코사인 유사도 0도 (같은 방향) 1 90도 (직교) 0 180도 (반대 방향) -1 1에 가까울수록 유사한 의미를 가진다. 0벡터는 방향이 없으므로 코사인 유사도를 계산할 수 없다. 4-3. 유클리디안 vs 코사인 base = np.array([1.0, 2.0]) # 크기만 두 배 [2, 4] → 거리: 2.236 코사인: 1.0 # 위치 이동 [2, 3] → 거리: 1.414 코사인: 0.992 구분 측정 대상 특징 유클리디안 거리 위치 차이 크기 차이를 그대로 반영 코사인 유사도 방향의 유사성 같은 방향이면 크기가 달라도 1 자연어 처리에서는 주로 코사인 유사도를 쓰고, 상대적인 크기를 볼 때 유클리디안 거리를 쓴다. 예를 들어 경제·정치 관련 단어 수로 기사를 표현하면, 길이가 짧은 기사와 긴 기사도 주제 비율이 같으면 코사인 유사도는 높게 나온다. 4-4. 자카드 유사도 (Jaccard index) 문서·문장 간 겹치는 토큰의 비율을 측정한다. J(A, B) = |A ∩ B| / |A ∪ B| = |A ∩ B| / (|A| + |B| - |A ∩ B|) 집합을 사용하므로 같은 단어가 여러 번 나와도 한 번만 센다. ( ["사과", "사과", "바나나"] vs ["사과", "원숭이", "원숭이"] → 0.333) 같은 집합 → 1.0 / 겹치지 않음 → 0.0 / 두 공집합 → 이 함수에서는 1.0으로 정의 4-5. 레벤슈타인 거리 (Levenshtein distance) 두 문자열이 얼마나 다른지를 나타내는 거리 로, 한 문자열을 다른 문자열로 바꾸는 데 필요한 삽입·삭제·치환의 최소 횟수 로 형태적 거리를 잰다. H O N D A H Y U N D A I 5. n-Gram n-Gram은 연속한 n개 단어를 하나로 묶어 보는 방법이다. 몇 개를 묶느냐에 따라 unigram·bigram·trigram 등으로 구분하며, 제한적이지만 문맥을 표현할 수 있다. an adorable little boy is spreading smiles unigrams : an, adorable, little, boy, is, spreading, smiles bigrams : an adorable, adorable little, little boy, boy is, ... trigrams : an adorable little, adorable little boy, little boy is, ... 4-grams : an adorable little boy, adorable little boy is, ... 5-1. n-Gram 활용 활용 내용 연어 처리 금융 통화 위원회 , 국회 의원 , 고객 서비스 처럼 함께 쓰이는 단어 묶기 Language Modeling 단어 시퀀스의 확률 계산에 사용 분야(Domain)에 따라 단어들의 확률 분포가 다르다. (금융 분야는 금융 용어가 많이 등장) 분야에 적합한 코퍼스를 사용하면 언어 모델의 성능이 높아질 수 있다. 반대로 훈련 코퍼스에 따라 성능이 달라지는 것은 언어 모델의 약점으로 분류되기도 한다.