공개 보고서에 필요한 정보만
65분 안팎
학습 목표
개인정보의 목적·보관·공개 범위를 구분합니다.
개념
내부 데이터와 공개 결과를 따로 설계합니다
독서 현황을 동료에게 공유하려고 내부 저장 파일을 그대로 보내면 이름과 이메일까지 따라갈 수 있습니다. 공개 보고서의 목적은 어떤 책을 얼마나 읽었는지 확인하는 것입니다. 이 목적을 기준으로 필요한 열을 정하고 나머지는 조회 결과에 넣지 않습니다. 이번 브라우저 과제는 title과 pages만 내보내고 로컬 미션은 내부 기록의 연결을 위해 id, title, pages, completed 네 열을 내보냅니다. 출력 계약이 다르다는 사실을 먼저 확인합니다.
owner_name과 owner_email은 가상 인물의 이름과 연락처입니다. 예제에서 실제 사람을 식별하지 않더라도 같은 구조를 실제 자료에 적용하면 공개 범위에 대한 검토가 필요합니다. 학습 중에는 제공된 가상 값만 사용합니다. 이메일에서 일부 글자를 가리는 방식도 원본 연락처를 다른 형태로 내보내는 일이므로 이번 보고서에는 이메일 열 자체를 포함하지 않습니다. 데이터를 덜 내보내는 선택이 코드로 확인될 수 있어야 합니다.
열을 제외했다고 모든 재식별 가능성이 없어지는 것은 아닙니다. 제목이나 ID가 다른 자료와 연결되면 개인을 추정할 수 있습니다. 이번 트랙의 id는 독서 기록 ID이며 공개용 보고서 검증을 위한 값입니다. 실제 조직에서는 공개 대상과 연결 가능성을 검토해 ID를 제외하거나 공개용 식별자를 따로 둘 수 있습니다. 이 레슨의 결과를 익명화 완료라고 부르지 않고 정해진 필드 최소화 계약을 만족한 실습 결과라고 설명합니다.
열 목록을 허용 목록으로 사용합니다
SELECT title, pages FROM reading_records는 공개할 두 속성의 허용 목록입니다. SELECT *에서 나중에 owner_email만 지우는 방법은 새로운 개인 관련 열이 추가될 때 놓치기 쉽습니다. 열 목록을 명시하면 표 구조가 확장되어도 보고서의 열은 자동으로 늘어나지 않습니다. 공개 결과를 만든 뒤에도 실제 cursor.description의 열 이름을 검사하면 조회문을 수정하는 과정에서 범위가 넓어진 실수를 발견할 수 있습니다.
화면에서 이메일을 숨기는 것과 응답에 이메일을 넣지 않는 것은 다릅니다. 사용자는 개발자 도구나 저장 파일에서 화면에 보이지 않는 응답 필드도 읽을 수 있습니다. 따라서 결과 생성 단계에서 제외하고 JSON으로 직렬화한 결과에 가상 값이 없는지도 확인합니다. 테스트에는 이름과 이메일에 서로 다른 표식을 넣습니다. 이메일의 @ 문자만 검사하면 이름 누출이나 다른 표현의 연락처를 놓칠 수 있습니다.
CREATE VIEW public_reading AS SELECT title, pages FROM reading_records처럼 조회를 이름 붙일 수도 있습니다. 뷰는 보고서의 열 계약을 재사용하는 도구입니다. 하지만 SQLite DB 파일을 함께 제공하면 받는 사람이 원본 표를 조회할 수 있으므로 뷰만으로 접근 권한이 분리되지는 않습니다. 공개할 때는 원본 DB 파일 대신 허용 열에서 만든 결과만 전달합니다. 접근 통제와 조회 구조는 서로 관련되지만 같은 기능은 아닙니다.
수집 목적과 보관 범위를 문서화합니다
필요한 데이터를 수집하는 시점과 공개하는 시점은 다릅니다. 실습 미션의 개인정보 문서에는 가상 owner 필드를 사용하는 목적이 누출 방지 테스트임을 적습니다. 실제 연락처 수집 기능은 만들지 않습니다. 내부 JSON은 앞 모듈의 다섯 필드 계약을 유지하고 owner 값은 별도 fixture에서만 제공합니다. 이렇게 분리하면 기존 파일 검증을 느슨하게 바꾸지 않고 공개 보고서 경계를 시험할 수 있습니다.
보관 범위는 어느 저장소에 무엇이 남는지 구체적으로 적습니다. 미션은 보고서 생성 때마다 메모리 SQLite 연결을 만들고 종료할 때 닫습니다. 이름·이메일을 가진 DB 파일을 디스크에 만들지 않으며 로깅 함수에서도 해당 값을 출력하지 않습니다. 원본 독서 JSON은 유지되고 보고서 함수는 이를 읽기만 합니다. 메모리 DB 종료가 원본 JSON 삭제를 뜻하지 않는다는 점도 문서에 적습니다.
삭제 기준은 목적이 끝났을 때 어떤 자료를 정리할지 설명하는 운영 약속입니다. 이 실습에서는 검증이 끝난 가상 owner fixture를 실제 개인 자료로 바꾸지 않으며 필요 없는 결과 파일은 학습자가 제거할 수 있습니다. 실제 서비스의 기간을 이 예제로 단정하지 않습니다. docs/privacy.md에는 원본 파일, 메모리 DB, 공개 JSON 결과, 로그 각각의 범위를 적고 공개 결과를 따로 저장했다면 그 사본의 정리도 포함합니다.
조회 실행과 공개 응답의 경계를 확인합니다
Python sqlite3 연결에서 데이터를 넣을 때 execute의 ? 자리표시자를 사용합니다. 제목에 작은따옴표가 포함되어도 SQL 명령으로 해석되지 않고 하나의 값으로 들어갑니다. report.sql은 제공된 신뢰할 수 있는 조회문 파일이며 사용자 입력을 실행하는 API가 아닙니다. 학습자가 보고서 SQL을 수정할 수 있지만 임의의 외부 SQL 파일을 안전하게 실행하는 기능이라고 설명하지 않습니다.
결과는 열 이름과 행 값으로 딕셔너리를 만들고 JSON으로 표현합니다. NULL은 Python에서 None, JSON에서 null이 됩니다. 문자열 NULL로 바꾸면 자료형이 달라집니다. SQL 브라우저 화면의 NULL 표시는 채점용 텍스트 표현이고 미션의 JSON null과 구분합니다. completed도 0 또는 1의 정수를 유지하므로 true로 임의 변환하지 않습니다. 다음 모듈의 HTTP 응답에서도 이 공개 필드 계약을 재사용할 수 있습니다.
보고서 생성 중 예외가 발생해도 연결을 finally에서 닫고 원본 파일을 덮어쓰지 않습니다. 오류 메시지에 입력 행 전체를 넣지 않는 이유는 실패한 데이터가 로그로 새어 나갈 수 있기 때문입니다. ValueError: REPORT_COLUMNS는 공개 열 계약 불일치를 뜻하도록 설계합니다. SQL 문법 오류는 SQLite 예외 이름과 테스트 위치로 추적하며 owner 값을 진단 목적으로 출력하지 않습니다.
제출물을 사람과 테스트가 나누어 검토합니다
자동 검사는 공개 열 이름이 정확한지, 반환 JSON과 캡처한 로그에 가상 이름·이메일이 없는지 확인합니다. 쪽수 NULL과 빈 목록에서도 같은 구조가 유지되어야 합니다. 완료 필터 결과는 Python으로 완료 1인 행을 골라 ID 정렬한 목록과 비교합니다. 합계는 NULL이 아닌 쪽수만 더합니다. 기존 정상 JSON에서의 변환과 조회용 NULL fixture에서의 동작을 별도 테스트로 검증합니다.
테스트가 통과해도 수집 목적과 삭제 기준의 설명이 정확한지는 사람이 읽어야 합니다. 문서의 키워드가 있다는 사실은 내용의 적절성까지 보장하지 않습니다. SELECT *를 피한 이유, 뷰가 원본 파일의 접근을 막지 못하는 이유, 내부 입력과 공개 결과의 차이를 스스로 설명해 봅니다. API나 로그를 추가할 때도 이 경계를 유지해야 한다는 점을 리뷰 기준으로 삼습니다.
브라우저 과제에서는 가상 소유자 값이 있는 표에서 제목과 쪽수만 ID 순서로 조회합니다. 빈 표에서는 출력이 없고 동일 제목의 여러 기록은 각각 반환합니다. 중복을 없애는 DISTINCT를 추가하면 기록 수가 달라질 수 있으므로 요구되지 않은 변환은 하지 않습니다. 미션에서는 앞 모듈 solution 전체를 이어받고 신규 보고서 함수와 개인정보 문서만 확장합니다. 결과만 공유하고 실습 DB나 내부 파일을 함께 보내지 않는 이유를 끝에 설명합니다.
따라하기
민감 열 제외
공유 목적에 필요한 제목과 쪽수만 반환합니다.
CREATE TABLE r(id TEXT,title TEXT,pages INTEGER,owner_name TEXT,owner_email TEXT); INSERT INTO r VALUES('b','별빛',20,'가상나','n@example.invalid'),('a','달빛',10,'가상가','g@example.invalid'); SELECT title,pages FROM r ORDER BY id;실행 결과
달빛 10 별빛 20
뷰와 원본 구분
뷰 결과 뒤에 원본 이름이 조회됩니다. 뷰가 원본 접근을 막지 않는 것을 가상 자료로 확인합니다.
CREATE TABLE r(title TEXT,pages INTEGER,owner_name TEXT); INSERT INTO r VALUES('책',5,'가상인물'); CREATE VIEW public_reading AS SELECT title,pages FROM r; SELECT title,pages FROM public_reading; SELECT owner_name FROM r;실행 결과
책 5 가상인물
NULL 보존
NULL과 0을 구분하고 연락처는 결과에서 제외합니다.
CREATE TABLE r(id TEXT,title TEXT,pages INTEGER,owner_email TEXT); INSERT INTO r VALUES('a','미측정',NULL,'x@example.invalid'),('b','영쪽',0,NULL); SELECT title,pages FROM r ORDER BY id;실행 결과
미측정 NULL 영쪽 0
확인 문제
실습
가상 소유자 이름·이메일이 있는 reading_records에서 title·pages만 ID 오름차순으로 반환합니다. ID는 정렬에만 쓰고 결과에 포함하지 않습니다.
모범 답안
SELECT title, pages FROM reading_records ORDER BY id;
더 읽기
면접 질문
- 공개 보고서에서 이름·이메일을 제외한 뒤에도 점검할 범위를 설명합니다.