캡슐화(Encapsulation)란? 데이터와 그 데이터를 다루는 동작을 하나로 묶고, 내부 사정은 감춘 채 바깥에는 꼭 필요한 기능만 열어두는 것. 여기에는 두 가지 의미가 겹쳐 있다. 하나는 관련된 데이터(필드)와 로직(메서드)을 한 클래스 안에 모으는 '묶기'고, 다른 하나는 그 내부를 바깥에서 함부로 건드리지 못하게 막는 '숨기기'이다. 숨기기 쪽만 따로 떼어서 정보 은닉(information hiding)이라고 부르기도 한다. 자판기를 떠올려보면 쉽다. 우리는 돈을 넣고 버튼을 누르는 것만 할 수 있지, 안에 손을 넣어서 음료를 꺼내거나 동전 개수를 직접 바꿀 수는 없다. 내부에서 재고를 어떻게 관리하고 거스름돈을 어떻게 계산하는지 몰라도 쓰는 데 전혀 지장이 없고, 누가 내부를 망가뜨릴 수도 없다. 이게 캡슐화가 잘 된 상태이다. 캡슐화가 없으면 생기는 일 은행 계좌를 클래스로 예를 들어보자. public class BankAccount{ public int balance; // 누구나 직접 읽고 쓸 수 있음 } //프로젝트 어딘가에서.. account.balance = -1000000; // 잔액이 마이너스가 돼도 아무도 못 막음 account.balance += account; // 입금 로직이 코드 곳곳에 흩어짐 문제는 크게 2가지이다. 첫째, "잔액은 음수가 될 수 없다"같은 규칙을 강제할 방법이 없다. 둘째, 나중에 "입금할 때마다 로그를 남겨야 한다"는 요구사항이 생기면 balance를 건드리는 코드를 프로젝트 전체에서 찾아내서 전부 고쳐야 한다. 하나라도 빠뜨리면 그게 바로 버그가 된다. 캡슐화를 제대로 적용하려면 public class BankAccount{ private int balance; // 클래스 밖에서는 직접 접근 불가 public void deposit(int amount) { if (amount <= 0) { throw new IllegalArgumentException("입금액은 0보다 커야 합니다."); } balance += amount; public void withdraw(int amount){ if (amount <= 0 || amount > balance) { balance -= amount; } public int getBalance() { return balance; } } 이제 balance는 private이라 밖에서 손댈 수 없고, 잔액을 바꾸는 방법은 deposit()과 withdraw()를 거치는 것 뿐이다. 그 두 메서드가 규칙을 검사하여, 누가 이 객체를 어떻게 쓰든 잔액이 음수가 되는 일은 생길 수 없다. 이렇게 객체가 언제나 지켜야 하는 조건을 불변식(invariant)이라고 하는데, 캡슐화의 가장 큰 목적이 바로 객체가 자기 불변식을 스스로 지키게 만드는 것이다. 부수적인 이득도 크다. 로그를 남겨야 하면 deposit() 한 곳만 고치면 되고, 잔액이 이상하게 나오면 이 클래스의 메서드 몇 개만 살펴보면 된다. 더 나아가서 나중에 잔액을 int필드 대신 거래 내역 목록으로 저장하고 합계를 계산하는 방식으로 내부를 통째로 갈아엎어도, 공개된 메서드의 모양만 그대로면 이 클래스를 쓰는 코드는 한 줄도 바꿀 필요가 없다. 변경의 영향이 클래스 경계 안에서 멈춘다. 이걸 어려운 말로 "결합도를 낮춘다"라고 한다. getter/setter만 붙이면 캡슐화일까? 필드를 private으로 바꾸고 getter/setter 붙이면 캡슐화 끝 <- 이라고 초반에 생각하기 쉬운데, 이건 반만 맞다. public class BankAccount { private int balance; public int getBalance() { return balance; } public void setBalance(int balance) { this.balance = balance; } } 위의 코드를 보면, 필드는 분명 private인데 account.setBalance(-1000000) 이 여전히 가능하다. 한 단계 거쳐갈 뿐, public필드랑 똑같이 아무것도 보호하지 못하고 있따. 캡슐화의 핵심은 필드를 숨기는 문법이 아니라, 데이터를 꺼내주는 대신 객체에게 일을 시키는 구조를 만드는 데 있다. 이걸 "Tell, Don't Ask(묻지 말고 시켜라)" 원칙이라고 부른다. // Ask: 데이터를 꺼내서 밖에서 판단하고 조작 if (account.getBalance() >= price) { account.setBalance(account.getBalance() - price); } // Tell: 객체에게 할 일을 시킴 account.withdraw(price); Ask방식은 "잔액이 충분한가"라는 규칙이 계좌 클래스 바깥에 있어서, 결제하는 곳마다 같은 검사를 반복해야 하고 결국 누군가는 빼을 수 있다. 반면, Tell방식은 규칙이 객체 안에 딱 한 번만 존재한다. 오해하지 말아야 할 건 getter 자체가 나쁜 건 아니다. 잔액을 화면에 보여주는 것처럼 값을 읽기만 하는 용도로만 괜찮다. 문제는 꺼낸 값으로 밖에서 판단하고 상태를 바꾸는 코드이다. 같은 이유로 메서드 이름도 데이터 조작이 아니라 의도가 드러나게 짓는 게 좋다. order.setStatus(OrderStatus.CANCELLED) 보다 order.cancel() 이 훨씬 나은데, cancel() 안에서는 "이미 배송된 주문은 취소할 수 없다"같은 규칙을 자연스럽게 검사할 수 있따. setStatus()는 어떤 값이든 받아주는 통로일 뿐이다. 놓치기 쉬운 구멍: 내부 컬렉션 노출 필드를 private으로 잘 막아놨는데도 캡슐화가 새는 경우가 있다. public class Order { private final List<OrderItem> items = new ArrayList<>(); public List<OrderItem> getItems() { return items; // 내부 리스트를 그대로 넘겨줌 } } order.getItems().clear(); // 밖에서 주문 항목을 통째로 지울 수 있다 getter가 내부 리스트의 참조를 그대로 넘기면, 받은 쪽에서 add()나 clear()로 계속 속을 마음대로 바꿀 수 있다. 여기서 final은 "다른 리스트로 바꿔치기할 수 없다"는 뜻이지 "리스트 내용을 못 바꾼다"는 뜻이 아니라서 도움이 안된다. 이럴 땐 읽기 전용으로 감싸서 돌려주고, 추가는 검증이 들어간 전용 메서드로만 하게 만든다. public List<OrderItem> getItems() { return Collections.unmodifiableList(items); // 수정 시도 시 예외 발생 } public void addItem(OrderItem item) { // 수량 확인, 최대 개수 제한 같은 규칙을 여기서 검사 items.add(item); } List뿐 아니라 Map이나 배열처럼 내용을 바꿀 수 있는 객체를 반환할 때는 항상 같은 문제를 의식해야한다. 접근 제어자, 그리고 다른 언어 Java에서 캡슐화를 구현하는 도구가 접근 제어자이다. private은 같은 클래스 안에서만, 아무것도 안 붙인 default(package-private)는 같은 패키지 안에서만, protected는 같은 패키지와 상속받은 자식 클래스에서, public은 어디서나 접근할 수 있다. 실무 원칙은 단순하다. 일단 가장 좁은 private으로 시작하고, 정말 필요할 때만 넓힌다. 한번 public으로 열어둔 건 누가 어디서 쓰고 있을지 몰라서 나중에 닫기가 정말 어렵다. 다른 언어도 방식만 다를 뿐 생각은 같다. Python은 언어 차원에서 접근을 막아주지 않아서 _balance 처럼 밑줄을 붙여 "내부용이니 건드리지마"라는 약속으로 표현하고, JavaScript는 #balance 처럼 #을 붙이면 진짜 private 필드가 된다. 무분별한 게터/세터 모든 private 속성에 getter와 setter를 만들어 public으로 열어 놓는다면 외부에서 언제든 값을 꺼내고 변경이 자유롭다. 이런 부분을 무분별한 게터/세터 라고 표현한다. 무분별한 게터/세터가 쓰이게 되면 캡슐화가 깨지게 된다. 캡슐화의 핵심은 데이터 은닉이지만 좀 더 풀어서 설명하면 -> 객체의 속성은 객체가 정한 규칙대로 바뀌게 허용한다 이다. 게터(Getter) 캡슐화가 무너져 있는 예시 List<Integer> resultsList = calculator.getResults(); resultList.add(1); // Calculator 클래스 내부의 속성을 외부에서 조작 위의 코드는 캡슐화가 지켜진 코드가 아니다. 캡슐화를 지키면서 조회하는 방법은 아래의 형식으로 수정이 불가능한 복제본을 만들어서 반환해주는 방법이 있다. 이런 방법을 방어적 복사 라고 한다. public List<Integer> getResults() { return List.copyOf(resultList); } 반환 방식 | 받은 쪽에서 add()하면 | 내부 리스트 | 나중에 내부가 바뀌면 받은 리스트는| |--|--|--|--| return resultList; | 성공 | 같이 바뀜(캡슐화 깨짐) | 같이 바뀜| return new ArrayList<>(resultList); | 성공(사본만 바뀜) | 그대로 | 안바뀜| return Collections.unmodifiableList(resultList); | 예외 발생 | 그대로 | 같이 바뀜| return List.copyOf(resultList); |예외 발생|그대로|안 바뀜| List.copyOf(resultList) 는 이 문제를 두 겹으로 막아준다. 첫째, 이름은 그대로 새 리스트를 만들어서 원본의 요소를 옮겨 담는다. 그래서 받은 쪽이 쥐는 건 원본과 분리된 사본이다. 둘째, 그 사본은 수정이 불가능한 리스트이다. 실수로 고치려 해도 원본은 절대 안 바뀌고, 고치려는 시도는 바로 예외로 드러난다. 내부 상태를 지키려고 미리 복사해서 내보낸다고 해서 방어적 복사라고 부른다. Collections.unmodifiableList 도 외부 수정을 막는다는 점은 같다. 다만 이건 사본이 아니라 원본을 읽기 전용으로 들여다보는 창(view)이다. 그래서 Calcualtor 안에 나중에 결과가 추가 되면, 이미 받아 간 리스트에서도 그게 보인다. 대신 복사를 안하니 비용은 들지 않는다. new ArrayList<>(resultList) 는 반대로 복사는 하지만 수정은 가능. 받은 쪽이 add()를 하면 사본만 조용히 바뀐다. 캡슐화는 지켜지지만, 호출한 쪽은 결과를 바꿨다고 착각할 수 있다. 계산 결과처럼 "그 시점의 목록을 보여주는" 용도라면 List.copyOf 가 제일 무난하다. Collections.unmodifiableList VS List.copyOf Collections.unmodifiableList 는 원본을 감싼 읽기 전용 창(view)이다. 이 창을 통해서는 못 고치지만, 창 너머의 원본이 바뀌면 창에 보이는 내용도 같이 바뀐다. 즉 "이 참조로는 못 고친다"는 뜻이지, "내용이 절대 안바뀐다"는 뜻은 아니다. List<Integer> results = calculator.getResults(); // 뷰(unmodifiableList)를 받음 System.out.println(results); // [3, 7] // ...그 뒤 새 계산을 해서 Calculator 내부 리스트에 10이 추가되면 System.out.println(results); // [3, 7, 10] ← 받아 둔 리스트의 내용이 바뀌어 있음 List.copyOf 로 받았다면 두 번째 출력도 [3,7]이다. 이 차이가 실제 문제로 번지는 대표적인 경우가 순회이다. for (Integer result : calculator.getResults()) { // 뷰를 순회하는 도중에 if (result < 0) { calculator.removeFirstResult(); // 원본이 바뀌면 ConcurrentModificationException } } 뷰는 원본을 그대로 들여다보고 있다. 그래서 순회 중에 원본이 바뀌면 Java가 이를 감지해서 ConcurrentModificationException을 던질 수 있다. copyOf로 받은 사본을 들고 있었다면, 원본이 바뀌어도 사본은 그대로라 안전하다. 받아 둔 목록이 나도 모르게 바뀌는 것 자체도 디버길할 때 꽤 헷갈리는 부분 중 하나가 될 수 있다. 대신 뷰에는 확실한 장점이 있다. unmodifiableList는 감싸기만 하니까 리스트가 아무리 커도 비용이 거의 없다. 반면 copyOf는 호출할 때마다 요소 수만큼 복사한다. 그래서 리스트가 아주 크고 getter가 자주 불리는데 받은 쪽이 바로 읽고 버린다면 뷰가 더 효율적이다. 항상 최신 상태를 보여주는 창이 필요할 때도 뷰가 오히려 맞는 선택이다. 세터(Setter) 세터 또한 기본적으로 만들지 않는 것을 권장 setResults()처럼 속성을 통빼로 바꾸는 메서드 대신, 외부에서 실제로 필요한 동작만 메서드로 열어줘야 한다.
Por Lawrence Dauchy Publicado em 30 de setembro de 2026 Se estás a considerar contratar um serviço de SEO para ChatGPT, provavelmente queres saber duas coisas: quanto custa e o que, exatamente, estás a comprar. Os preços variam bastante porque ainda não existe uma definição universal para este tipo de serviço. Resposta curta Um serviço profissional de SEO para ChatGPT pode custar desde algumas centenas até vários milhares de euros por mês, dependendo do número de mercados, páginas, produtos, idiomas, concorrentes e plataformas de IA analisadas. Projetos pontuais, como uma auditoria de visibilidade em IA, tendem a ser mais baratos. Programas contínuos que incluem pesquisa, conteúdo, otimização técnica, construção de autoridade e acompanhamento de menções exigem um orçamento mensal maior. O fator decisivo não deve ser apenas o preço. É preciso perceber quais atividades estão incluídas e como o fornecedor mede progresso. Resumo SEO para ChatGPT, também chamado frequentemente de GEO, otimização para pesquisa com IA ou otimização para motores de resposta, procura aumentar a probabilidade de uma empresa ser encontrada, compreendida e considerada relevante por sistemas de IA. O custo depende principalmente da profundidade do trabalho. Uma auditoria simples pode identificar problemas, enquanto um programa completo pode envolver dezenas de conteúdos, melhorias técnicas, análise de concorrentes, desenvolvimento de autoridade externa e acompanhamento contínuo da visibilidade. Antes de comparar propostas, compara o escopo. O que deves saber Não existe uma tabela de preços universal para SEO para ChatGPT. Auditorias pontuais normalmente custam menos do que programas mensais. Produção de conteúdo costuma representar uma parte importante do orçamento. Mais países, idiomas e produtos aumentam significativamente o trabalho necessário. A otimização técnica é apenas uma parte do processo. Nenhum fornecedor sério pode garantir que o ChatGPT vai recomendar uma empresa. Quanto custa SEO para ChatGPT? Na prática, existem quatro faixas comuns de serviço: auditorias, acompanhamento básico, programas completos e projetos empresariais de grande escala. Os intervalos abaixo devem ser tratados como referências de planeamento, não como preços oficiais do mercado. Tipo de serviço Faixa de preço indicativa Normalmente adequado para Auditoria pontual 500 € a 2.500 € Pequenas empresas que querem identificar problemas Programa básico mensal 500 € a 1.500 €/mês Sites pequenos e empresas com poucos produtos Programa completo 1.500 € a 5.000 €/mês Ecommerce, SaaS e empresas competitivas Projeto empresarial 5.000 €+/mês Grandes catálogos, vários mercados e múltiplos idiomas Uma pequena empresa com vinte páginas não exige o mesmo volume de análise que uma loja online com 30.000 produtos em seis países. Também existe uma diferença importante entre receber recomendações e pagar pela implementação. Uma auditoria pode explicar o que precisa de ser alterado. Um programa completo executa essas alterações continuamente. O que está normalmente incluído num serviço de SEO para ChatGPT? Um serviço completo começa normalmente com uma análise da presença atual da marca. O objetivo é perceber como a empresa aparece quando potenciais clientes fazem perguntas relevantes em sistemas como ChatGPT, Google AI Overviews, AI Mode, Gemini ou Perplexity. Depois dessa análise inicial, o trabalho pode envolver várias áreas. Auditoria de visibilidade em IA A equipa cria consultas representativas do comportamento do cliente. Por exemplo, uma empresa de software de faturação pode testar perguntas como: Qual é o melhor software de faturação para pequenas empresas? Que alternativas existem ao produto X? Que software de faturação funciona para freelancers? Qual é o melhor programa para emitir faturas online? O objetivo não é testar apenas o nome da empresa. Uma marca pode aparecer quando alguém pesquisa diretamente pelo seu nome e continuar completamente ausente das perguntas comerciais que realmente influenciam decisões. Análise dos concorrentes Também é necessário perceber quais empresas aparecem quando a tua marca não aparece. Uma boa análise tenta descobrir: quais concorrentes são mencionados; em que tipos de perguntas aparecem; quais páginas desses concorrentes parecem relevantes; quais fontes externas mencionam essas empresas; quais temas estão associados a cada marca. Este exercício ajuda a transformar uma expressão vaga como "melhorar a visibilidade no ChatGPT" numa lista concreta de oportunidades. Pesquisa de tópicos e perguntas SEO tradicional trabalha bastante com palavras-chave. A pesquisa para motores de resposta exige também trabalhar com perguntas, comparações, problemas, entidades e intenções. Uma única palavra-chave pode gerar dezenas de perguntas diferentes. Por isso, o trabalho costuma incluir a identificação de consultas como: melhor ferramenta para determinada tarefa; alternativa a uma marca; comparação entre dois produtos; solução para um problema específico; produto para determinado setor; fornecedor numa determinada região; opção para uma determinada faixa de preço. Essas perguntas ajudam a definir quais conteúdos precisam de existir. Quanto custa a criação de conteúdo para ChatGPT? O conteúdo pode representar uma parte significativa do orçamento porque aumentar a cobertura temática exige mais do que editar algumas páginas existentes. Dependendo do projeto, podem ser necessárias páginas comerciais, comparações, explicadores, perguntas frequentes, conteúdos de categoria, páginas de solução e artigos especializados. O custo depende de quatro variáveis principais: Quantidade. Produzir cinco artigos por mês custa menos do que produzir quarenta. Profundidade. Um artigo básico exige menos pesquisa do que uma comparação técnica detalhada. Especialização. Temas financeiros, jurídicos, médicos ou técnicos exigem maior cuidado editorial. Idiomas. Traduzir mecanicamente não é suficiente para muitos mercados. Perguntas, concorrentes e terminologia podem mudar de país para país. Um programa barato que publica muito conteúdo superficial pode criar volume sem aumentar a utilidade do site. O serviço inclui SEO técnico? Pode incluir, mas convém confirmar isso antes de assinar qualquer contrato. O conteúdo precisa de estar acessível aos sistemas que o pesquisam e recuperam. Um especialista pode analisar fatores como: indexação; robots.txt; sitemaps; renderização; estrutura HTML; links internos; dados estruturados; canonicalização; velocidade; páginas duplicadas; arquitetura de categorias; disponibilidade do conteúdo no HTML. O erro comum é transformar SEO para ChatGPT num projeto puramente técnico. Adicionar schema, alterar robots.txt ou instalar uma ferramenta não cria automaticamente autoridade nem faz com que uma empresa seja recomendada. A infraestrutura permite que o conteúdo seja encontrado. A relevância da informação e a reputação da entidade continuam a ser essenciais. SEO para ChatGPT inclui menções externas e digital PR? Nos programas mais completos, deveria existir pelo menos uma análise da presença externa da empresa. Os sistemas de IA podem encontrar informação sobre uma marca em muitos locais diferentes. O próprio site é apenas uma dessas fontes. Por isso, uma estratégia pode incluir trabalho destinado a aumentar referências legítimas em: publicações especializadas; diretórios relevantes; páginas de parceiros; associações; estudos; comparações; entrevistas; comunidades relevantes; bases de conhecimento públicas. O objetivo deve ser construir evidência consistente sobre a empresa. Comprar centenas de menções artificiais simplesmente para "enganar a IA" cria risco e raramente representa uma estratégia sustentável. O que faz o preço aumentar? O preço cresce quando aumenta a quantidade de trabalho necessária para cobrir o mercado. Fator Impacto típico no custo Número de produtos ou serviços Mais páginas e consultas para analisar Número de países Exige pesquisa específica para cada mercado Número de idiomas Exige localização e conteúdo adicional Concorrência Mercados competitivos exigem maior profundidade Volume de conteúdo Aumenta produção, edição e pesquisa Implementação técnica Pode exigir developers Digital PR Exige pesquisa e outreach adicional Monitorização Mais prompts e plataformas aumentam o volume de dados Um ecommerce internacional tende, por isso, a pagar mais do que uma empresa local com um único serviço. Qual é a diferença entre uma auditoria e um serviço mensal? Uma auditoria responde principalmente à pergunta: "Onde estão os problemas e oportunidades?" Um serviço mensal responde também a: "Quem vai executar o trabalho?" Uma auditoria pode entregar: análise da visibilidade atual; mapa de concorrentes; oportunidades de conteúdo; problemas técnicos; lacunas de autoridade; prioridades; plano de implementação. Depois disso, alguém precisa de executar o plano. Um programa mensal pode transformar essas recomendações em conteúdos, melhorias técnicas, atualização de páginas e construção de presença externa. Para empresas com uma boa equipa interna, uma auditoria pode ser suficiente. Para empresas sem recursos internos, a execução costuma representar a maior parte do valor do contrato. Como saber se uma proposta está cara? Olha primeiro para o volume real de trabalho. Uma proposta de 3.000 € por mês pode ser cara se entregar apenas quatro relatórios automáticos. A mesma mensalidade pode ser competitiva se incluir pesquisa contínua, vinte conteúdos, otimização técnica, acompanhamento de concorrentes, estratégia editorial e implementação. Antes de comparar fornecedores, pede respostas concretas para estas perguntas: Quantas páginas ou conteúdos serão criados ou atualizados? Quantas consultas de IA serão acompanhadas? Quais plataformas serão analisadas? Quantos concorrentes serão monitorizados? O trabalho técnico está incluído? A implementação está incluída? O trabalho externo de autoridade está incluído? Quantos mercados e idiomas estão cobertos? Que métricas
블로그를 다시 시작하는 이유는 42에서 배운 경험을 이제는 취업에 관련된 상황으로 만들기 위함이다. 42 는 프랑스에 있는 42에콜 이라는 프로그래밍 학습시스템입니다(전세계적으로 있습니다). 필자는 42경산에서 2년동안 42 안에서 시스템프로그래밍과 스스로 뭘 모르고 다른사람과 소통하는 방법을 배웠습니다. 42에서 배운 기술은 취업을 할때 선호받는 프로젝트가 아닌것 같다. 지금 까지는 나도 프라이드를 가지고 있었지만 지금 현실은 그렇지 않다. 지금은 AI 시대이다. AI 시대에는 42에서 훈련한 고민하고 빠르게 학습하고 적용하는 기술을 신기술에 접목시켜야한다. 시스템프로그래밍 , 보안에만 집착을 하다보면 시야가 좁아지기 때문이다. 서론이 너무 길었다. go 를 배워보자. 지금 내가 처한 기술적 상황을 분석해보자. go 언어에 대해서 하나도 모른다. window 프로그램에서 코딩 해본적이 없다. c 로 시스템프로그램 및 서버를 만들어본 경험이 있다. 디버깅툴로(linux)에서 디버깅을 하고 상황을 분석하고 측정할 수 는 능력을 가지고 있다. 지금과 같은 상황에서 내가 가장 go 를 빠르게 배울 수 있는 방법은 window agent(프로세스를 추적하는 agent) 를 만드는 것이다. 이유는 다음과 같다. 시스템프로그램을 이 익숙하다. 저수준을 좋아해서 흥미가 있다. 좋아하고 흥미가 있는건 빠르게 만들 수 있다. package main import ( "errors" "fmt" "strings" ) type Task struct { ID int Title string Done bool } type Store struct { Tasks []Task NextID int } func addTask(s *Store, title string) (Task, error) { task := strings.TrimSpace(title) if task == "" { return Task{}, errors.New("title is empty") } s.NextID++ tmp := Task{ ID: s.NextID, Title: task, Done: false, } s.Tasks = append(s.Tasks, tmp) return tmp, nil } func main() { store := Store{ NextID: 0, } var title string for { fmt.Scanln(&title) if title == "exit" { break } tmp, check := addTask(&store, title) if check != nil { fmt.Println(check) continue } fmt.Println(tmp) } } 간단히 입출력을 하는 데이터베이스를 만들어보니 go의 특징을 알았다. go 는 매우 쉬워 보이지만 매우 어렵다. 객체지향을 따라하지만 완벽한 객체 지향은 아니다. c 랑 비슷하면서도 매우 다르다. 알게 된 사실 go의 슬라이스의 크기가 정해지지 않는다면 크기보다 더큰곳에 접근한다면 error 가 나게 된다는 사실이다. c 언어에서는 약간 넘어가도 문제가 생기지 않았지만 go 는 문제가 바로 생긴다. go의 함수는 반환값을 여러개 받을 수 있다. error 는 type이지 반환값이 아니다. 객체지향을 추구하지만 객체지향이 아니다. 아직까지는 go의 장점을 모르겠다.
로컬 Git만으로도 버전 관리는 가능하지만 여러 컴퓨터에서 작업하거나 다른 사람과 협업하려면 원격 저장소(remote repository) 가 필요하다. GitHub는 로컬 Git 저장소를 인터넷에 연결해 백업, 공유, 코드 리뷰, 이슈 관리까지 할 수 있게 해준다. 이 글에서는 로컬 저장소와 원격 저장소가 어떤 관계를 갖는지부터 push , clone , fetch , pull , Issue, Pull Request까지 하나의 흐름으로 정리한다. 로컬 저장소와 원격 저장소 구조를 단순화하면 다음과 같다. 내 컴퓨터 GitHub Local Repository <--------> Remote Repository main main │ │ └─ origin/main ---------------┘ Git은 분산 버전 관리 시스템 이기 때문에 로컬 컴퓨터에도 전체 Git 히스토리가 있고 원격 저장소에도 히스토리가 존재한다. 인터넷이 끊겨도 로컬에서 commit , branch , merge , log 같은 작업을 계속할 수 있는 이유가 여기에 있다. 원격 저장소 연결하기 GitHub에서 빈 저장소를 만든 뒤 로컬 저장소에 원격 주소를 등록한다. git remote add origin https://github.com/USER/REPOSITORY.git origin 은 원격 저장소 URL에 붙인 별명 이다. 반드시 origin이어야 하는 것은 아니지만 가장 널리 쓰이는 관례다. 등록 상태는 다음 명령으로 확인할 수 있다. git remote -v 예시는 다음과 같다. origin https://github.com/USER/REPOSITORY.git (fetch) origin https://github.com/USER/REPOSITORY.git (push) push: 로컬 커밋을 원격 저장소로 올리기 로컬에서 커밋을 만든 것만으로는 GitHub에 자동 반영되지 않는다. Local A ── B ── C ↑ main Remote A ── B ↑ origin/main C를 원격 저장소로 보내려면 push한다. git push origin main 처음 브랜치를 올리면서 upstream을 연결하려면 다음 명령이 자주 사용된다. git push -u origin main 이후에는 단순히 다음처럼 사용할 수 있다. git push origin/main은 무엇인가 origin/main 은 GitHub 서버에 직접 존재하는 브랜치 자체가 아니라 내 로컬 Git이 기억하고 있는 원격 main의 마지막 확인 상태 를 나타내는 remote-tracking branch다. 예를 들어 로컬에서만 새 커밋을 만들면 다음처럼 보일 수 있다. A ── B ── C ↑ ↑ origin/main main 이 상태는 "로컬 main이 원격에서 알고 있는 main보다 한 커밋 앞서 있다"고 해석할 수 있다. push가 성공하면 원격 저장소도 C까지 올라가고 로컬의 origin/main 도 그 상태를 반영한다. clone: 저장소를 히스토리까지 복제하기 새로운 컴퓨터에서 기존 원격 저장소로 작업을 시작하고 싶다면 clone 을 사용한다. git clone https://github.com/USER/REPOSITORY.git clone은 단순히 현재 소스 파일만 다운로드하는 것이 아니다. 프로젝트 파일 커밋 히스토리 브랜치 정보 원격 저장소 연결 정보 까지 함께 가져온다. 기본적으로 원격 저장소는 origin 이라는 이름으로 등록된다. cd REPOSITORY git remote -v 오픈소스 프로젝트를 분석할 때도 clone을 사용하면 현재 파일뿐 아니라 과거 변경 이력까지 확인할 수 있다. fetch: 원격의 최신 정보를 가져오기만 한다 협업 중 다른 사람이 원격 저장소에 커밋을 올렸다고 하자. Local main A ── B Remote main A ── B ── C 원격 상태를 확인하고 싶을 때 fetch 를 사용할 수 있다. git fetch origin fetch의 핵심은 다음과 같다. 원격 저장소의 커밋과 참조 정보를 가져오지만 현재 작업 브랜치와 Working Directory를 자동으로 병합하지 않는다. fetch 후에는 다음과 비슷해진다. A ── B ── C ↑ ↑ main origin/main 따라서 fetch 자체만으로는 일반적인 merge conflict가 발생하지 않는다. 로컬 작업 파일을 바로 합치는 단계가 없기 때문이다. 차이를 확인하려면 다음처럼 비교할 수 있다. git log main..origin/main --oneline 또는 git diff main..origin/main 원격 변경을 먼저 살펴보고 병합하고 싶을 때 유용하다. pull: 가져오고 현재 브랜치에 반영하기 pull 은 원격 변경을 가져온 뒤 현재 브랜치에 통합한다. git pull 개념적으로는 보통 다음 흐름으로 이해하면 된다. git fetch ↓ merge 또는 rebase 정확한 통합 방식은 Git 설정과 옵션에 따라 merge 또는 rebase가 될 수 있다. 중요한 점은 pull은 실제로 현재 브랜치에 원격 변경을 반영하므로 충돌이 발생할 수 있다는 것 이다. fetch → 다운로드/원격 상태 갱신 → Working Directory를 바로 합치지 않음 pull → 원격 변경 가져오기 → 현재 브랜치에 통합 → 상황에 따라 conflict 가능 협업 프로젝트에서 작업을 시작하기 전에 원격 변경을 확인하는 습관이 좋다. git switch main git pull 두 사람이 같은 저장소에서 작업한다면 간단한 협업 흐름은 다음과 같다. 개발자 A 개발자 B │ │ ├─ clone ├─ clone │ │ ├─ 작업 + commit ├─ 작업 + commit │ │ ├─ push ───────────▶ GitHub ◀────────┤ │ │ └─ pull ◀────────── GitHub ──────────┘ 여기서 양쪽이 동일한 코드 영역을 서로 다르게 수정하면 pull 또는 merge 시점에 충돌이 발생할 수 있다. 따라서 실제 팀에서는 보통 main에 직접 작업하기보다 브랜치를 만들고 Pull Request를 통해 병합한다. .gitignore : 저장소에 넣지 않을 파일 제외하기 모든 파일을 Git으로 관리할 필요는 없다. 대표적으로 다음 파일은 저장소에 포함하지 않는 경우가 많다. 환경 변수 파일 빌드 결과물 패키지 캐시 IDE 개인 설정 로그 파일 비밀키와 인증 정보 프로젝트 루트에 .gitignore 를 만들 수 있다. # 환경 변수 .env .env.* # Node.js node_modules/ # Python __pycache__/ .venv/ # 로그 *.log # 빌드 결과 build/ dist/ 중요한 점은 .gitignore 가 이미 Git이 추적 중인 파일을 자동으로 추적 해제하지는 않는다는 것 이다. 이미 커밋된 파일을 앞으로 추적하지 않게 하려면 상황에 따라 다음처럼 인덱스에서 제거해야 한다. git rm --cached .env 그리고 .gitignore 에 추가한 뒤 커밋한다. API Key, Token, Password 같은 민감 정보는 애초에 커밋하지 않는 것이 가장 중요하다. GitHub Issue로 작업 단위를 관리하기 GitHub Issue는 버그, 기능 개선, 질문, 할 일 등을 기록할 수 있는 작업 관리 도구다. 예를 들어 다음과 같은 이슈를 만들 수 있다. 제목: 검색 결과 정렬 기능 추가 내용: - 최신순/인기순 정렬 지원 - URL query parameter와 동기화 - 모바일 UI 확인 Issue에는 다음 정보를 연결할 수 있다. Assignee: 담당자 Label: bug, enhancement, docs 등 분류 Milestone: 일정 단위 Comment: 진행 과정과 논의 AI 코딩 에이전트를 사용할 때도 작업 지시를 대화창에만 남기는 것보다 Issue를 기준으로 작업하게 하면 무엇을 왜 수정했는지 기록 하기 쉽다. GitHub CLI gh 로 브라우저 없이 작업하기 GitHub CLI를 사용하면 터미널에서 GitHub 기능을 제어할 수 있다. 로그인은 다음 명령으로 시작한다. gh auth login Issue 생성 예시는 다음과 같다. gh issue create \ --title "검색 결과 정렬 기능 추가" \ --body "최신순과 인기순 정렬을 구현한다." 최근 이슈를 확인하려면 다음처럼 사용할 수 있다. gh issue list Git 명령과 gh 명령의 역할은 다르다. git → 로컬/원격 Git 저장소의 버전 관리 gh → GitHub의 Issue, Pull Request, Repository 등 서비스 기능 제어 Pull Request는 "이 브랜치를 병합해 주세요"라는 요청이다 새 기능을 브랜치에서 개발했다고 하자. git switch -c feature/search-sort # 작업 git add . git commit -m "검색 결과 정렬 기능 추가" git push -u origin feature/search-sort 이제 원격 저장소에는 main 과 feature/search-sort 가 모두 존재한다. main A ── B \ C ── D feature/search-sort Pull Request(PR)는 개념적으로 다음 요청이다. feature/search-sort 에서 작업한 내용을 검토한 뒤 문제가 없다면 main 에 병합해 주세요. PR에서는 다음을 확인할 수 있다. 어떤 파일이 바뀌었는지 어떤 줄이 추가/삭제되었는지 커밋 목록 리뷰 의견 자동 테스트 결과 병합 가능 여부 GitLab에서는 비슷한 기능을 Merge Request 라고 부른다. PR의 일반적인 협업 흐름 Issue 생성 ↓ 작업 브랜치 생성 ↓ 코드 수정 ↓ commit ↓ push ↓ Pull Request 생성 ↓ 리뷰 및 수정 ↓ merge ↓ 로컬 main에서 pull GitHub CLI로 PR을 만들 수도 있다. gh pr create \ --base main \ --head feature/search-sort \ --title "검색 결과 정렬 기능 추가" \ --body "관련 기능 구현 및 테스트 완료" PR이 GitHub에서 merge되었다면 로컬 main은 아직 그 사실을 모를 수 있다. git switch main git pull 이렇게 원격 main의 최신 병합 결과를 로컬에 가져온다. 실무에서 자주 쓰는 협업 패턴 혼자 작업하더라도 다음 흐름을 사용하면 기록과 검토가 쉬워진다. # 1. main 최신화 git switch main git pull # 2. 작업 브랜치 생성 git switch -c feature/profile-edit # 3. 작업 후 커밋 git add . git commit -m "프로필 수정 기능 추가" # 4. 원격에 브랜치 업로드 git push -u origin feature/profile-edit # 5. PR 생성 gh pr create --base main --fill 병합이 완료되면 다음처럼 정리할 수 있다. git switch main git pull git branch -d feature/profile-edit 핵심 정리 GitHub는 Git과 별개의 원격 저장소 서비스이며 GitLab, Bitbucket 같은 대안도 있다. origin 은 원격 저장소 주소에 붙이는 관례적인 별명이다. push 는 로컬 커밋을 원격 저장소로 업로드한다. clone 은 소스 코드뿐 아니라 Git 히스토리까지 포함해 저장소를 복제한다. fetch 는 원격 변경을 가져오지만 현재 Working Directory에 자동 병합하지 않는다. pull 은 원격 변경을 가져와 현재 브랜치에 통합하므로 충돌이 발생할 수 있다. .gitignore 는 추적하지 않을 파일을 정의하지만 이미 추적 중인 파일을 자동으로 해제하지 않는다. Issue는 작업을 기록하고, Pull Request는 브랜치의 변경을 검토한 뒤 병합하도록 요청하는 협업 흐름의 중심이다.
Por Lawrence Dauchy Publicado el 30 de septiembre de 2026 Una agencia SEO IA ayuda a una empresa a mejorar su visibilidad tanto en buscadores tradicionales como en sistemas de búsqueda y respuesta basados en inteligencia artificial. En 2027, esto puede incluir Google Search, Google AI Overviews, Google AI Mode, ChatGPT Search y otros motores que recuperan información de la web para responder preguntas. La diferencia principal frente a una agencia SEO tradicional está en lo que se mide. Las posiciones orgánicas siguen siendo importantes, pero también hay que entender cuándo una marca aparece en respuestas generadas por IA, qué competidores son mencionados, qué páginas se utilizan como fuente y qué información sobre la empresa puede recuperar cada sistema. Respuesta corta Una buena agencia SEO IA combina SEO técnico, estrategia de contenidos, análisis de entidades, optimización para búsquedas generativas y medición de visibilidad en respuestas de IA. Google mantiene que las prácticas fundamentales de SEO siguen siendo relevantes para AI Overviews y AI Mode. También ha explicado que estas experiencias pueden utilizar query fan-out, generando varias búsquedas relacionadas para construir una respuesta. Por eso, una estrategia preparada para 2027 necesita cubrir temas y preguntas con mayor profundidad que una estrategia centrada únicamente en una palabra clave. Resumen Elegir una agencia SEO IA exige comprobar tres cosas: qué entiende realmente por optimización para IA, qué trabajo ejecuta y cómo mide los resultados. Una agencia seria debería empezar con una auditoría de visibilidad, revisar la base técnica del sitio, analizar cómo distintas plataformas interpretan la empresa, desarrollar contenido que responda preguntas reales y medir cambios de forma recurrente. También debería mantener expectativas razonables. Ninguna agencia puede garantizar que ChatGPT, Google AI Mode o cualquier otro sistema mencione una marca en una respuesta concreta. Lo que debes saber El SEO tradicional sigue siendo la base. La búsqueda con IA continúa dependiendo en gran medida de contenido rastreable, indexable y útil. La visibilidad en IA necesita métricas propias. Posición, tráfico y clics cuentan solo una parte de la historia. Las consultas son cada vez más complejas. Una sola pregunta puede generar varias búsquedas relacionadas. La autoridad de marca importa. La información coherente sobre una empresa facilita su identificación. El contenido debe responder preguntas completas. Una página optimizada solo alrededor de una keyword puede quedarse corta. Nadie puede garantizar una cita. Los sistemas de IA cambian continuamente y utilizan múltiples señales. ¿Qué es exactamente una agencia SEO IA? Una agencia SEO IA es un proveedor especializado en conseguir que una empresa sea fácil de descubrir, comprender y recuperar dentro de experiencias de búsqueda asistidas por inteligencia artificial. En SEO clásico, una parte importante del trabajo gira alrededor de consultas, páginas, rankings, clics y conversiones. Una agencia orientada a IA amplía ese análisis a preguntas como: ¿Aparece la empresa cuando alguien pregunta por los mejores proveedores de su categoría? ¿La IA entiende correctamente qué vende? ¿Menciona primero a competidores? ¿Utiliza páginas de la empresa como referencia? ¿Relaciona la marca con los productos, servicios y mercados correctos? Este tipo de análisis suele denominarse GEO, Generative Engine Optimization, o AEO, Answer Engine Optimization. La terminología cambia entre proveedores, pero el objetivo práctico es aumentar la probabilidad de que la información correcta sobre una empresa pueda encontrarse y utilizarse dentro de sistemas generativos. Google ha aclarado que sus funciones generativas siguen apoyándose en los principios fundamentales del SEO. Esto significa que una estrategia de IA que ignora rastreo, indexación, calidad de contenido y estructura del sitio empieza desde una base débil. ¿Qué servicios debería ofrecer una agencia SEO IA? Los servicios concretos varían, pero una estrategia completa suele empezar por diagnóstico y terminar en medición recurrente. Auditoría de visibilidad en IA El primer paso consiste en establecer una línea base. La agencia debería crear conjuntos de preguntas que representen distintas etapas de compra. Por ejemplo: mejores empresas para resolver un problema concreto; alternativas a una marca conocida; comparación entre varias soluciones; recomendaciones para un sector; herramientas para una función específica; proveedores disponibles en una región; preguntas sobre características, precios o casos de uso. Después se comprueba qué marcas aparecen, cómo son descritas y qué fuentes se utilizan. Una auditoría útil también identifica discrepancias. Una empresa puede posicionarse correctamente en Google y seguir teniendo poca presencia en determinadas respuestas generadas por IA. SEO técnico preparado para búsqueda con IA Los sistemas necesitan poder acceder al contenido antes de interpretarlo. Por eso, una agencia debería revisar rastreo, indexación, renderizado, enlaces internos, canonicalización, sitemaps, robots.txt y el funcionamiento de CDN o sistemas de seguridad que puedan bloquear crawlers legítimos. OpenAI, por ejemplo, documenta OAI-SearchBot para el descubrimiento de contenido utilizado en ChatGPT Search. Bloquear determinados rastreadores puede afectar la capacidad de una plataforma para recuperar contenido directamente. En Google, la situación es algo diferente. Google indica que una página debe estar indexada y ser elegible para mostrarse con un snippet para poder aparecer como enlace de apoyo en AI Overviews o AI Mode. El principio práctico es sencillo: antes de trabajar sobre prompts y menciones de IA, la infraestructura debe permitir que las páginas importantes sean descubiertas. ¿Cómo cambia la estrategia de contenidos con la búsqueda basada en IA? La búsqueda generativa aumenta el valor de cubrir una intención de forma completa. Google ha documentado que AI Overviews y AI Mode pueden utilizar query fan-out. El sistema puede dividir una consulta inicial en varias búsquedas relacionadas para encontrar información adicional. Imagina una búsqueda como: "¿Cuál es el mejor software de inventario para una pequeña tienda Shopify?" El sistema puede necesitar información adicional sobre precio, integraciones, tamaño del negocio, características, soporte o alternativas antes de construir la respuesta. Una estrategia SEO IA debería anticipar esas preguntas relacionadas dentro de una arquitectura lógica de contenidos. Esto no significa crear cientos de páginas casi idénticas para cada variación. Google advierte precisamente contra producir contenido a escala principalmente para manipular rankings o respuestas generativas. Una página completa, clara y bien estructurada suele ser más útil que veinte páginas superficiales alrededor de pequeñas variaciones de una keyword. ¿Qué papel tienen las entidades y las menciones de marca? Los sistemas necesitan entender qué representa una marca y cómo se relaciona con otras entidades. Para una empresa, eso puede incluir: nombre comercial; productos; categorías; fundadores; ubicación; mercados atendidos; especialidades; perfiles oficiales; referencias externas; asociaciones con determinados temas. La consistencia importa. Si una empresa se describe de forma completamente diferente en su web, perfiles comerciales y publicaciones externas, resulta más difícil construir una representación clara de la entidad. Una agencia SEO IA debería revisar esas inconsistencias y buscar oportunidades para fortalecer la información disponible sobre la marca. Esto también explica por qué el trabajo fuera del propio sitio puede importar. La percepción de una empresa no se construye únicamente a partir de lo que afirma en su página de inicio. Fuentes independientes, publicaciones especializadas, comunidades, reseñas y menciones editoriales pueden aportar contexto adicional. ¿Cómo debería medir resultados una agencia SEO IA? Medir exclusivamente rankings de Google es insuficiente para este tipo de proyecto. Una estrategia completa puede observar cuatro grupos de métricas. Rendimiento en búsqueda tradicional Aquí entran impresiones, clics, posiciones, páginas indexadas y tráfico orgánico. Estas métricas siguen siendo importantes porque la búsqueda tradicional continúa formando parte del proceso de descubrimiento. Presencia en respuestas de IA Se pueden ejecutar conjuntos estables de preguntas de forma periódica y registrar si la marca aparece. También conviene medir qué competidores reciben más menciones y para qué categorías. Citaciones y fuentes Otra capa consiste en identificar qué dominios y páginas son utilizados como fuentes. Si un competidor aparece repetidamente, el análisis debería intentar entender qué evidencia disponible en la web facilita esa presencia. Resultados comerciales La visibilidad solo tiene valor si conecta con objetivos reales. Dependiendo del negocio, eso puede significar leads, solicitudes de presupuesto, ventas, registros, pruebas gratuitas o búsquedas de marca. En 2026 Google amplió sus herramientas de medición relacionadas con experiencias generativas de Search, otra señal de que la visibilidad en estos entornos está pasando de ser una idea experimental a una parte medible del trabajo de búsqueda. ¿Cómo elegir una agencia SEO IA en 2027? La mejor forma de evaluar una agencia es pedir que explique su proceso con precisión. Una respuesta basada únicamente en "crear contenido optimizado para ChatGPT" debería generar dudas. El trabajo real es más amplio. Busca una agencia que pueda explicar cómo conecta investigación, SEO técnico, contenido, autoridad de entidad y seguimiento de respuestas generativas. También debería distinguir claramente entre lo documentado y lo observado. Por ejemplo, Google documenta públicamente el uso potencial de query fan-out en sus funciones generativas. En cambio, las ponderaciones exactas utilizadas para decidir qu
[시나리오] 정수 편집 버퍼 EditBuffer. 내용이 커지면 eb_grow() 가 realloc 으로 버퍼를 키운다. "실행 취소(undo)"를 위해 eb_snapshot() 이 현재 상태를 undo[] 에 저장한다. [기대 동작] 스냅샷을 찍고 값을 많이 추가한 뒤, 정리(eb_free)에서 누수 없이 해제하고 정상 종료. [증상] eb_snapshot() 이 저장하는 것은 "그 시점의 data 포인터(원시 주소)"다. 이후 eb_grow() 가 realloc 으로 버퍼를 옮기면(주소 변경), 저장해 둔 스냅샷 포인터는 '이미 해제된 옛 블록'을 가리키게 된다(댕글링). 정리 시 eb_free() 는 현재 data 를 해제한 뒤 undo[] 의 옛 포인터들도 free 하는데, 그 블록들은 realloc 이 이미 해제한 것이라 → double free / invalid pointer 로 glibc abort(SIGABRT). #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_UNDO 8 typedef struct { int *data; size_t len, cap; int *clipboard; int *undo[MAX_UNDO]; int undo_n; } EditBuffer; static void eb_init(EditBuffer *e) { e->cap = 4; e->len = 0; e->undo_n = 0; e->data = malloc(e->cap * sizeof(int)); if (!e->data) { perror("malloc"); exit(1); } /* data 바로 뒤에 놓이는 별도 할당. data 가 힙 맨 끝(top)이 아니게 되어 이후 realloc 이 제자리 확장 대신 '이동'을 택하게 만든다(→ 옛 블록 해제). */ e->clipboard = malloc(e->cap * sizeof(int)); if (!e->clipboard) { perror("malloc"); exit(1); } } static void eb_snapshot(EditBuffer *e) { if (e->undo_n < MAX_UNDO) e->undo[e->undo_n++] = e->data; } static void eb_grow(EditBuffer *e, size_t need) { size_t nc = e->cap; while (nc < need) nc *= 2; int *p = realloc(e->data, nc * sizeof(int)); if (!p) { perror("realloc"); free(e->data); exit(1); } e->data = p; e->cap = nc; } static void eb_push(EditBuffer *e, int v) { if (e->len == e->cap) eb_grow(e, e->len + 1); e->data[e->len++] = v; } static void eb_free(EditBuffer *e) { free(e->data); free(e->clipboard); for (int i = 0; i < e->undo_n; i++) { free(e->undo[i]); } e->undo_n = 0; e->data = NULL; } int main(void) { EditBuffer e; eb_init(&e); for (int i = 0; i < 3; i++) eb_push(&e, i); eb_snapshot(&e); for (int i = 0; i < 4000; i++) eb_push(&e, i); printf("len=%zu cap=%zu head=%d tail=%d\n", e.len, e.cap, e.data[0], e.data[e.len - 1]); eb_free(&e); printf("done\n"); return 0; } 이곳에 버그가 있습니다. 이를 gdb를 활용하여 디버깅을 해야하는게 이번 문제입니다. (make파일을 사용하여 gdb를 실행하였습니다.) 저는 우선 시나리오와 기대동작을 보았습니다. [시나리오] 정수 편집 버퍼 EditBuffer. 내용이 커지면 eb_grow() 가 realloc 으로 버퍼를 키운다. "실행 취소(undo)"를 위해 eb_snapshot() 이 현재 상태를 undo[] 에 저장한다. [기대 동작] 스냅샷을 찍고 값을 많이 추가한 뒤, 정리(eb_free)에서 누수 없이 해제하고 정상 종료. 그 후, EditBuffer 구조체의 데이터들을 살피고, gdb로 실행하여 오류가 나는 곳을 포착하였습니다. 입력 \> make gdb NAME=10_realloc_dangling \> run 출력 \>Program received signal SIGABRT, Aborted. ... \>0x0000555555555483 in eb_free (e=0x7fffffffe050) at challenges/10_realloc_dangling/bug.c:85 위 에러를 보고 eb_free() 함수 중 버퍼의 undo 포인터에 접근할 때 오류가 나는 것을 확인하였습니다. 이를 해결하기 위해 undo에 값을 넣는 방식을 먼저 찾아보았습니다. eb_snapshot() 함수에서 undo에 data의 주소를 넣고있었습니다. static void eb_snapshot(EditBuffer *e) { if (e->undo_n < MAX_UNDO) e->undo[e->undo_n++] = e->data; } 현재로선 이상하지 않은 코드입니다. 다만, data의 주소가 변경된다면 댕글링 포인터가 됩니다. 이를 확인하기 위해 각 함수에서 printf로 data의 주소를 출력해 봤습니다. 그 결과, eb_grow()를 실행한 뒤 data의 주소가 바뀌는 것을 확인했습니다. eb_init()에는 data가 힙의 맨 끝(top)이 아니게 되어 이후 realloc이 제자리 확장 대신 이동을 택하게 만든다 는 주석이 있었고, 실제로 eb_grow()에서 data에 realloc을 사용하고 있었습니다. 요약하자면, undo에 data의 주소를 저장하였지만, data를 realloc하는 과정에서 확장이 아닌, 이동을 하여 기존 data주소를 갖고 있던 undo는 댕글링 포인터가 되어, free할 때 오류가 발생하게 되었습니다. 이를 해결하기 위해 저는 undo에 data의 주소를 담는 것이 아닌, data를 복사하여 새로 할당받아두어 undo에 저장하는 방식으로 해결하였습니다. if (e->undo_n < MAX_UNDO) e->undo[e->undo_n++] = e->data; -> if (e->undo_n < MAX_UNDO) { int* newData = malloc(sizeof e->data); newData = memcpy(newData, e->data, sizeof e->data); e->undo[e->undo_n++] = newData; } malloc으로 데이터를 복사할 공간을 생성. memcpy로 data를 새 메모리에 복사. undo에 새 메모리의 주소를 저장. 아래는 최종 코드입니다. #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_UNDO 8 typedef struct { int *data; size_t len, cap; int *clipboard; int *undo[MAX_UNDO]; int undo_n; } EditBuffer; static void eb_init(EditBuffer *e) { e->cap = 4; e->len = 0; e->undo_n = 0; e->data = malloc(e->cap * sizeof(int)); if (!e->data) { perror("malloc"); exit(1); } /* data 바로 뒤에 놓이는 별도 할당. data 가 힙 맨 끝(top)이 아니게 되어 이후 realloc 이 제자리 확장 대신 '이동'을 택하게 만든다(→ 옛 블록 해제). */ e->clipboard = malloc(e->cap * sizeof(int)); if (!e->clipboard) { perror("malloc"); exit(1); } } static void eb_snapshot(EditBuffer *e) { if (e->undo_n < MAX_UNDO) { int* newData = malloc(sizeof e->data); newData = memcpy(newData, e->data, sizeof e->data); e->undo[e->undo_n++] = newData; } } static void eb_grow(EditBuffer *e, size_t need) { size_t nc = e->cap; while (nc < need) nc *= 2; int *p = realloc(e->data, nc * sizeof(int)); if (!p) { perror("realloc"); free(e->data); exit(1); } e->data = p; e->cap = nc; } static void eb_push(EditBuffer *e, int v) { if (e->len == e->cap) { eb_grow(e, e->len + 1); } e->data[e->len++] = v; } static void eb_free(EditBuffer *e) { free(e->data); free(e->clipboard); for (int i = 0; i < e->undo_n; i++) { free(e->undo[i]); } e->undo_n = 0; e->data = NULL; } int main(void) { EditBuffer e; eb_init(&e); for (int i = 0; i < 3; i++) eb_push(&e, i); eb_snapshot(&e); for (int i = 0; i < 4000; i++) eb_push(&e, i); printf("len=%zu cap=%zu head=%d tail=%d\n", e.len, e.cap, e.data[0], e.data[e.len - 1]); eb_free(&e); printf("done\n"); return 0; }
BX 디자인의 꽃은 브랜드의 경험을 손으로 만질 수 있는 오프라인 인쇄물로 확장하는 것 벡터(Vector) 기반의 필수 툴인 일러스트레이터(Illustrator)를 활용해 브랜드의 첫인상인 '명함'을 디자인 분석 및 계획 수립 명함의 대상 선정 앞/뒷면 레이아웃 구상: 앞면(브랜드 인지), 뒷면 (정보 전달) 인쇄 사양 고려 [이미지 예시] 오프라인 인쇄 디자인의 원칙 RGB가 아닌 CMYK 여백과 재단선 유의 실습 일러스트레이터를 실행하고 New Document 에서 크기를 일반적인 명함 규격(90 * 50 mm 또는 86 * 52mm)으로 설정합니다. 이때 컬러 모드는 CMYK , Raster Effects는 High (300ppi)로 세팅합니다. 앞/뒷면 작업을 위해 대지(Artboard) 수는 2개로 만듭니다. 앞면에는 브랜드 로고와 어울리는 그래픽 요소를 배치하고, 뒷면에는 도형 도구와 정렬 툴을 활용해 인적 사항을 깔끔하게 디자인합니다. 작업이 끝나면 인쇄소에 보낼 때 폰트가 깨지지 않도록 모든 텍스트를 선택한 뒤 Type > Create Outlines (단축키 Ctrl+Shift+O) 를 실행하여 글자를 도형화합니다. 최종 파일은 AI 형식과 PDF 형식으로 저장합니다. 느낀 점 실습을 하면서 제대로 익히고 싶었으나 무료 플랜을 이미 예전에 사용한 것인지 뜨지도 않고 14일 이내 전액 환불이라길래 결제했더니 실패했다고 뜨면서 결제는 2번이나 된 이런 어이없는 상황... 첫 난관에 봉착했지만 조금만 참으면 본 캠프 시작이니까... 이론 공부 위주로 해야겠다.
Node.js의 비동기 처리에 대해 공부한 뒤, 실제 클라이언트의 요청이 어떻게 서버까지 전달되는지 궁금해졌다. NestJS를 사용하면서 @Get() , @Post() , @Body() , @Param() 같은 기능은 사용해봤지만, 이것들이 HTTP에서 어떤 의미를 가지고 있는지 제대로 정리해본 적은 없었다. 이번에는 다음 내용을 중심으로 정리했다. Client와 Server Request와 Response HTTP Method HTTP Status Code Header와 Body Path Parameter와 Query Parameter IP와 Port Domain과 DNS HTTP와 HTTPS TCP 브라우저에 URL을 입력했을 때의 전체 흐름 1. Client와 Server 웹 통신의 가장 기본적인 구조는 Client와 Server다. 간단하게 생각하면 Client는 요청하는 쪽 , Server는 요청을 받아 처리하고 응답하는 쪽 이다. Client │ │ Request ▼ Server │ │ 처리 ▼ Database │ │ 결과 ▼ Server │ │ Response ▼ Client 예를 들어 React 애플리케이션에서 다음 요청을 보냈다고 생각해보자. axios.get("/api/posts"); 이 경우 요청을 보내는 React 애플리케이션은 Client 역할을 한다. 반대로 NestJS 서버는 요청을 받아 필요한 작업을 수행하고 결과를 반환한다. @Get('/posts') async getPosts() { return this.postService.findAll(); } 여기서 중요한 점은 Client가 반드시 브라우저일 필요는 없다는 것이다. 모바일 애플리케이션도 Client가 될 수 있고, 다른 Server가 특정 Server에게 요청을 보낸다면 그 순간에는 요청을 보내는 Server가 Client 역할을 할 수도 있다. 2. Request와 Response HTTP 통신에서는 Client가 Server에게 Request(요청) 를 보내고 Server는 처리 결과를 Response(응답) 로 반환한다. 예를 들어 10번 게시글을 조회한다고 해보자. Client │ │ GET /posts/10 │ Request ▼ Server 서버가 정상적으로 게시글을 찾았다면 다음과 같은 응답을 보낼 수 있다. Server │ │ 200 OK │ │ { │ "id": 10, │ "title": "Node.js 공부" │ } ▼ Client 즉 가장 기본적인 HTTP 통신 구조는 다음과 같다. Client ─── Request ───▶ Server Client ◀── Response ─── Server 3. HTTP Method HTTP 요청에는 해당 리소스에 어떤 작업을 하고 싶은지 나타내는 Method가 존재한다. 대표적으로 다음과 같은 Method를 사용한다. Method 일반적인 의미 예시 GET 조회 게시글 조회 POST 생성 게시글 작성 PUT 전체 교체/수정 게시글 전체 변경 PATCH 부분 수정 제목만 변경 DELETE 삭제 게시글 삭제 NestJS에서는 다음과 같이 사용한다. @Get('/posts') findAll() {} @Post('/posts') create() {} @Patch('/posts/:id') update() {} @Delete('/posts/:id') remove() {} URL이 같더라도 Method가 다르면 의미가 달라질 수 있다. GET /posts/10 → 10번 게시글 조회 PATCH /posts/10 → 10번 게시글 수정 DELETE /posts/10 → 10번 게시글 삭제 특히 PUT 과 PATCH 의 차이를 알아둘 필요가 있다. 일반적으로 PUT 은 리소스를 전체적으로 교체하는 의미로 사용하고, PATCH 는 리소스의 일부를 변경하는 의미로 사용한다. 예를 들어 10번 게시글의 제목만 변경한다면 다음과 같이 표현할 수 있다. PATCH /posts/10 { "title": "수정된 제목" } 4. HTTP Status Code Server는 데이터를 반환하면서 요청 처리 결과를 나타내는 HTTP Status Code 도 전달한다. 대표적인 Status Code는 다음과 같다. Status Code 의미 200 OK 요청 성공 201 Created 리소스 생성 성공 204 No Content 성공했지만 응답 Body 없음 400 Bad Request 잘못된 요청 401 Unauthorized 인증 정보가 없거나 유효하지 않음 403 Forbidden 인증은 되었지만 권한이 없음 404 Not Found 요청한 리소스를 찾을 수 없음 409 Conflict 현재 리소스 상태와 요청이 충돌 500 Internal Server Error 서버 내부 오류 크게 보면 다음처럼 구분할 수 있다. 2xx → 성공 4xx → Client 요청과 관련된 문제 5xx → Server 내부 문제 401과 403의 차이 공부하면서 특히 구분할 필요가 있다고 느낀 부분이다. 401 Unauthorized 쉽게 생각하면 "누구인지 확인할 수 없다." 이다. 예를 들어 로그인이 필요한 API인데 Access Token이 없거나 유효하지 않다면 401을 반환할 수 있다. 로그인하지 않음 ↓ 관리자 API 요청 ↓ 401 Unauthorized 403 Forbidden 403은 사용자가 누구인지는 확인했지만 해당 작업을 수행할 권한이 없는 경우 다. 일반 사용자 로그인 성공 ↓ 관리자 전용 API 요청 ↓ 403 Forbidden 따라서 간단하게 기억하면 다음과 같다. 401 → 인증(Authentication) 403 → 권한/인가(Authorization) 5. Header와 Body HTTP Request에는 Method와 URL뿐만 아니라 Header와 Body 같은 정보도 포함될 수 있다. 예를 들어: PATCH /posts/10 Headers Content-Type: application/json Authorization: Bearer <token> Body { "title": "수정된 제목" } Header Header에는 요청이나 응답에 대한 부가 정보 가 들어간다. 예를 들어: Content-Type: application/json 은 Body가 JSON 형식이라는 것을 나타낼 수 있다. JWT 기반 인증을 사용하는 경우에는 다음과 같은 Header를 자주 볼 수 있다. Authorization: Bearer <access-token> NestJS에서는 이후 Guard 같은 기능을 이용해 인증 정보를 검사하는 구조와 연결할 수 있다. Client │ │ Authorization: Bearer JWT ▼ NestJS │ ▼ Guard │ ├─ 인증 실패 → 401 │ └─ 인증 성공 → Controller Body Body에는 서버로 전달하려는 실제 데이터가 들어갈 수 있다. 예를 들어 회원가입이라면: POST /users Content-Type: application/json { "email": "test@test.com", "password": "1234" } email , password 같은 데이터가 Body에 들어간다. 단, Request Body가 항상 JSON인 것은 아니다. JSON은 웹 API에서 많이 사용하는 데이터 형식 중 하나다. 6. Path Parameter, Query Parameter, Body NestJS에서 자주 사용했던 다음 세 가지도 HTTP 요청과 연결된다. @Param() @Query() @Body() Path Parameter 특정 리소스를 지정할 때 많이 사용한다. GET /posts/10 여기서 10 이 Path Parameter다. @Get('/posts/:id') findOne(@Param('id') id: string) { return this.postService.findOne(id); } 쉽게 질문으로 생각하면: "누구를 대상으로 할 것인가?" 라고 볼 수 있다. /users/3 → 3번 사용자 /posts/10 → 10번 게시글 /products/25 → 25번 상품 Query Parameter 검색, 필터링, 정렬, 페이지네이션 등의 조건을 전달할 때 많이 사용한다. GET /posts?page=2&size=10 ? 뒤에 Query Parameter가 위치한다. @Get('/posts') findAll( @Query('page') page: string, @Query('size') size: string, ) { // ... } 다음과 같은 요청도 가능하다. GET /products?category=computer&sort=price 쉽게 생각하면: "어떤 조건으로 가져올 것인가?" 라고 볼 수 있다. Body 생성하거나 수정하려는 실제 데이터를 전달할 때 많이 사용한다. POST /products { "name": "키보드", "price": 50000 } NestJS에서는 다음과 같이 받을 수 있다. @Post('/products') create(@Body() body: CreateProductDto) { return this.productService.create(body); } 따라서 세 가지를 간단하게 정리하면: Path Parameter → 누구를? Query Parameter → 어떤 조건으로? Body → 어떤 데이터를? 7. IP Address 지금까지는 Client와 Server가 통신한다고만 생각했다. 하지만 인터넷에는 수많은 Server가 존재한다. Client가 특정 Server에게 데이터를 보내려면 "어느 컴퓨터로 데이터를 보내야 하는가?" 를 알아야 한다. 이때 사용하는 것이 IP Address 다. 쉽게 비유하면 IP는 집 주소 와 비슷하다. Client │ │ ▼ 203.0.113.10 Server IP 주소를 통해 네트워크상에서 통신하려는 대상 컴퓨터를 구분할 수 있다. 8. Port 하지만 하나의 Server에서는 여러 서비스가 동시에 실행될 수 있다. 예를 들어 한 Server에 다음 프로그램들이 존재한다고 생각해보자. Server NestJS MySQL Redis SSH IP만 가지고는 어떤 서비스와 통신하고 싶은지 구분할 수 없다. 그래서 Port 를 사용한다. IP를 집 주소라고 생각한다면 Port는 문 번호 라고 생각할 수 있다. 203.0.113.10 ├── :3000 → NestJS ├── :3306 → MySQL └── :6379 → Redis NestJS에서 다음 코드를 사용한 것도 같은 의미다. await app.listen(3000); 즉 NestJS 애플리케이션이 3000번 Port에서 요청을 받을 수 있도록 실행하는 것이다. 따라서: IP → 어느 컴퓨터인가? Port → 해당 컴퓨터의 어느 서비스인가? 라고 이해할 수 있다. 9. localhost 개발하면서 자주 사용했던 주소가 있다. http://localhost:3000 localhost 는 현재 자기 자신의 컴퓨터를 가리키는 Host Name 이다. IPv4에서는 대표적으로 다음 루프백 주소가 사용된다. 127.0.0.1 따라서: localhost:3000 은 쉽게 말하면 "내 컴퓨터의 3000번 Port에서 실행 중인 서비스" 를 의미한다고 볼 수 있다. 10. Domain과 DNS 실제로 웹사이트를 사용할 때 IP 주소를 직접 외워서 입력하지는 않는다. 대신 다음과 같은 주소를 사용한다. example.com google.com naver.com 이처럼 사람이 기억하기 쉽게 만든 이름이 Domain 이다. 하지만 실제 네트워크 통신을 위해서는 대상 Server의 IP 주소가 필요하다. 여기서 DNS(Domain Name System) 가 등장한다. DNS는 간단하게 Domain Name에 대응되는 IP 주소를 찾아주는 시스템 이라고 이해할 수 있다. example.com │ ▼ DNS │ ▼ 203.0.113.10 전화번호부에 비유하면 이해하기 쉽다. 사람 이름 → 전화번호 Domain → IP Address 11. DNS 실패와 404는 다르다 처음에는 DNS가 IP를 찾지 못하면 404 Not Found 가 발생한다고 생각했다. 하지만 둘은 완전히 다른 단계의 문제다. 404 Not Found 404는 서버까지 HTTP Request가 정상적으로 도착했지만 서버가 요청한 리소스를 찾지 못한 경우 다. Client ↓ DNS 성공 ↓ Server까지 요청 도착 ↓ GET /posts/999 ↓ 999번 게시글 없음 ↓ 404 Not Found 반면 DNS가 실패한다면: Domain ↓ DNS ↓ IP를 찾을 수 없음 X Server까지 도달하지 못함 즉 HTTP 요청을 처리할 서버의 위치 자체를 찾지 못한 것이다. 따라서 이 경우 Server가 보내는 HTTP 404 와는 다르다. 간단하게 기억하면: DNS 실패 → 서버 위치 자체를 찾지 못함 404 → 서버까지 도착했지만 요청한 리소스를 찾지 못함 12. HTTP와 HTTPS HTTP는 Client와 Server가 어떤 형식으로 요청하고 응답할지 정의한 통신 프로토콜 이다. 지금까지 배운 다음 내용들이 HTTP와 관련되어 있다. Method URL Header Body Status Code HTTP의 기본 Port는 80 이다. HTTP → Port 80 하지만 HTTP 자체만으로는 통신 내용을 안전하게 보호하기 어렵다. 예를 들어 로그인 요청을 생각해보자. { "email": "test@test.com", "password": "1234" } 네트워크를 통해 민감한 정보를 전달한다면 통신 내용을 보호할 필요가 있다. 그래서 HTTPS를 사용한다. HTTPS는 HTTP 통신을 TLS를 이용해 보호하는 방식 으로 이해할 수 있다. HTTP + TLS ↓ HTTPS HTTPS의 기본 Port는 443 이다. HTTP → 80 HTTPS → 443 13. HTTPS가 제공하는 것 HTTPS/TLS의 중요한 목적을 크게 세 가지로 정리할 수 있다. 기밀성 Confidentiality 통신 내용을 암호화하여 제3자가 내용을 쉽게 읽지 못하도록 보호한다. 무결성 Integrity 통신 과정에서 데이터가 변조되었는지 확인할 수 있도록 한다. 인증 Authentication 서버가 제공하는 인증서 등을 검증하여 Client가 접속하려는 Server의 신원을 확인하는 데 사용한다. 따라서 단순히 "HTTPS는 HTTP보다 보안이 좋다." 라고만 기억하기보다는 기밀성 무결성 인증 이라는 세 가지 키워드를 같이 기억하는 것이 좋다. 14. TCP는 무엇일까? HTTP가 요청과 응답의 형식을 정의한다면, 실제 데이터를 상대방에게 신뢰성 있게 전달하는 과정 도 필요하다. 여기서 TCP가 등장한다. 현재 단계에서는 TCP를 데이터를 상대방에게 신뢰성 있게 전달하기 위한 프로토콜 이라고 이해했다. TCP는 데이터의 순서와 손실 등을 관리하고 필요한 경우 재전송하는 방식으로 신뢰성 있는 데이터 전달을 제공한다. 따라서 HTTP와 TCP는 역할이 다르다. HTTP → 무엇을 어떤 형식으로 요청하고 응답할 것인가? TCP → 데이터를 상대방에게 신뢰성 있게 어떻게 전달할 것인가? IP → 데이터를 어느 컴퓨터로 보낼 것인가? 15. TCP 3-Way Handshake TCP에서는 데이터를 본격적으로 주고받기 전에 연결을 설정하는 과정이 있다. 대표적인 것이 3-Way Handshake 다. Client Server ───── SYN ───────────────▶ "연결할래?" ◀──── SYN + ACK ────────── "응, 나도 준비됐어" ───── ACK ───────────────▶ "확인했어" 연결 성립 순서는 다음과 같다. SYN ↓ SYN + ACK ↓ ACK 처음에는 마지막 단계도 SYN 이라고 생각했지만, 마지막은 ACK 다. 현재 단계에서는 내부 구조를 깊게 들어가기보다는 TCP는 통신 전에 3-Way Handshake를 통해 연결을 설정한다. 정도로 이해했다. 16. HTTP와 TCP를 혼동하지 않기 공부하면서 HTTP와 TCP의 역할이 조금 섞이기도 했다. 처음에는 HTTP가 IP 주소와 Port를 가지고 직접 서버와 연결해주는 것이라고 생각했다. 하지만 역할을 구분해서 보면 다음과 같다. HTTP → 요청/응답의 형식과 의미 TCP → 신뢰성 있는 데이터 전달 IP → 목적지 컴퓨터 Port → 목적지 컴퓨터에서 통신할 서비스 예를 들어: GET /posts/10 Authorization: Bearer <token> 같은 요청의 의미와 형식은 HTTP와 관련된다. 반면 해당 데이터를 네트워크를 통해 신뢰성 있게 전달하는 것은 TCP가 담당하는 영역이다. 각 계층의 역할을 구분해서 생각하는 것이 중요했다. 17. 브라우저에 URL을 입력하면 어떻게 될까? 마지막으로 지금까지 공부한 내용을 하나의 흐름으로 연결해봤다. 브라우저에 다음 주소를 입력했다고 생각해보자. https://example.com/posts/10 1단계: Domain 확인 example.com 이라는 Domain을 확인한다. 하지만 실제 통신을 위해서는 Server의 IP 주소가 필요하다. 2단계: DNS 조회 DNS를 이용하여 Domain에 대응되는 IP 주소를 찾는다. example.com ↓ DNS ↓ 203.0.113.10 3단계: 목적지 확인 HTTPS를 사용하고 있으므로 별도의 Port가 지정되지 않았다면 기본적으로 443 Port를 사용한다. 개념적으로 목적지를 다음과 같이 생각할 수 있다. 203.0.113.10:443 4단계: TCP 연결 Server와 데이터를 주고받기 위해 TCP 연결을 설정한다. SYN ↓ SYN + ACK ↓ ACK 3-Way Handshake가 완료되면 TCP 연결이 성립한다. 5단계: TLS HTTPS이므로 TLS를 이용해 보호된 통신을 위한 절차를 진행한다. 이 과정에서 인증서를 통한 Server 신원 확인과 이후 통신을 보호하기 위한 과정 등이 이루어진다. 6단계: HTTP Request 이제 Client가 HTTP Request를 보낼 수 있다. GET /posts/10 필요한 경우 Header도 포함된다. Authorization: Bearer <access-token> 7단계: Server 처리 NestJS 서버라면 요청이 Controller로 전달될 수 있다. @Get('/posts/:id') async findOne(@Param('id') id: string) { return this.postService.findOne(id); } 그리고 Service 등을 통해 필요한 비즈니스 로직을 수행한다. DB와 관련된 내용은 다음 학습에서 이어서 공부할 예정이다. 8단계: HTTP Response Server가 요청을 처리하면 Client에게 Response를 반환한다. 성공했다면: 200 OK { "id": 10, "title": "Node.js 공부" } 요청한 게시글이 없다면: 404 Not Found 인증이 필요한데 인증 정보가 없다면: 401 Unauthorized 등의 응답을 받을 수 있다. 전체 흐름 정리 지금까지 배운 내용을 하나로 연결하면 다음과 같다. https://example.com/posts/10 │ ▼ Domain │ ▼ DNS Domain → IP │ ▼ IP + Port Port 443 │ ▼ TCP 3-Way Handshake │ ▼ TLS 암호화 / 무결성 / 인증 │ ▼ HTTP Request GET /posts/10 │ ▼ Server │ ▼ Controller / Service │ ▼ 필요한 작업 처리 │ ▼ HTTP Response 200 / 404 등 │ ▼ Client 각각의 역할을 한 줄로 정리하면: Domain → 사람이 기억하기 쉬운 서버 이름 DNS → Domain에 대응되는 IP 주소를 찾음 IP → 어느 컴퓨터로 데이터를 보낼 것인지 Port → 해당 컴퓨터의 어느 서비스와 통신할 것인지 TCP → 데이터를 신뢰성 있게 전달 TLS → 통신을 암호화하고 보호 HTTP → Client와 Server가 요청하고 응답하는 방식 공부하면서 헷갈렸던 부분 이번에 공부하면서 특히 세 가지를 잘못 이해하고 있었다. 1. DNS를 실패하면 404가 발생한다? 아니다. DNS 실패는 Server의 IP 주소를 찾지 못해 Server까지 요청이 도달하지 못한 것이다. 404는 Server까지 요청이 정상적으로 도착했지만 요청한 리소스를 찾지 못한 경우
타이포그래피 폰트 사이즈 text-xs , text-sm , text-base , text-lg , text-xl , text-2xl ~ text-9xl 로 텍스트 크기 설정 XS~9XL로 단계별 사이즈가 분류되어 있으며, 실제 픽셀 값은 공식 문서에서 확인 가능 동적 값 지정: text-[13px] 처럼 대괄호 안에 원하는 값을 직접 입력 font size - Typography 폰트 컬러 text-red-500 , text-blue-300 등 색상 이름 + 명도(50~950) 로 텍스트 색상 설정 명도 500이 원색에 가장 가까우며, 숫자가 높을수록 진한 색상 동적 값 지정: text-[rgb(100,30,200)] 처럼 직접 색상값 명시 가능 color - Typography 폰트 웨이트 font-thin (100) ~ font-black (900)으로 텍스트 두께 설정 font-bold , font-extrabold 등 직관적인 이름으로 사용 font weight - Typography import "./App.css"; function App() { return ( <div className="App"> {/* 타이포그래피 */} <div className="text-xs text-red-700">text-xs</div> <div className="text-sm">text-sm</div> <div className="text-base font-bold">text-base</div> <div className="text-lg font-extrabold">text-lg</div> <div className="text-xl font-black">text-xl</div> <div className="text-2xl">text-2xl</div> <div className="text-[13px]">text-13px</div> </div> ); } export default App; 배경 색상 bg-amber-500 , bg-blue-400 등 bg- 접두사 + 색상 이름 + 명도로 설정 import "./App.css"; function App() { return ( <div className="App"> {/* 백그라운드 컬러 */} <div className="bg-amber-500">bg-amber-500</div> </div> ); } export default App; background-color - Backgrounds 사이즈 Width & Height w-20 , h-20 등 숫자 × 기본 간격(spacing = 4px)으로 실제 크기 계산 (w-20 = 80px) 대부분의 UI 디자인이 4px 단위를 기준으로 하기 때문에 이 방식을 채택 w-[80px] 와 같은 방식으로 직접 픽셀 지정 가능, w-full 로 부모 100% 너비 설정 가능 min-w- , max-w- 접두사로 최소/최대 너비도 설정 가능 import "./App.css"; function App() { return ( <div className="App"> {/* 사이즈 */} <div className="h-full w-20 bg-blue-500">w-20 h-full box</div> </div> ); } export default App; width - Sizing height - Sizing 여백 Padding p-5 (전방향, 5×4=20px), pt-5 (상), pr-5 (우), pb-5 (하), pl-5 (좌) px-10 (좌우), py-10 (상하)로 축 기준 설정 가능 Margin Padding과 동일한 방식으로 m- , mt- , mr- , mb- , ml- , mx- , my- 사용 import "./App.css"; function App() { return ( <div className="App"> {/* 여백 */} <div className="m-2 h-20 w-20 bg-green-500 px-5 py-5"> <div className="h-full w-full bg-red-500"></div> </div> </div> ); } export default App; padding- Spacing margin - Spacing 인용자료 출처 한입 리액트 실전 라이브러리 키트 - Zustand, Tanstack Query, Tailwind CSS