개발자답게 단순노동하기

2024년 7월 30일 화요일

Today I Learned

날짜

2024년 7월 30일 화요일

내용

사소한 듯 한 에러처리

어제 나는 분명 기능을 완성하고 갔다. 분명. 그런데 오늘 2차 QA를 시작하자마자 안 만들어진다. 하… 헛고생 했다는 생각에 1차 분노, QA 하는 분들의 시간을 낭비했다는 생각에 2차 분노했다. 화를 삭히고 열심히 원인을 찾았다.

  1. 네이버 API 호출 제한

네이버 검색광고에서 데이터를 불러올 때, 호출 횟수의 제한이 있다. 초당 20~30회가 넘어가면 429 코드가 반환되면서 “Too many requests” 라는 메시지를 뱉는다. 담당자의 답변에 의하면 1초 후 다시 시도하란다. 데이터를 불러올 떄, 이런 일이 발생하면 1초 후 로직을 다시 시작하도록은 구현해 놨다. 우선 테스트를 위해 요청 횟수를 적게 해놔서 발생할 일은 없을 거라고 예상했는데.. 오늘 QA 과정에서 문제가 됐다. 여러 명이 동시에 진행하는 QA에서 모두가 같은 네이버 계정을 사용했기 떄문이다. 100일치만 불러오느라 괜찮을 줄 알았는데 동시에 2~3 명이 요청을 보내니 에러가 떴고 내가 짠 코드가 제대로 작동 안하는게 확인됐다. 400일치가 제대로 돌아가는지 꼭 재차 확인해야겄다.

  1. 유일하지 않은 값

데이터베이스에 네이버 계정 정보를 불러올떄 “naver_customer_id” 라고 하는 네이버쪽 고유 아이디를 사용했다. 네이버에선 고유할지 모르나 우리쪽 데이터베이스에선 아니였다. 같은 네이버 계정이 여러 구글계정 밑에 등록될 수 있다는 걸 생각하지 못했다.

  1. 데이터 없음
1
2
3
4
5
6
7
def get_keyword_name_by_naver_keyword_id(naver_keyword_id: str, db: Session):
    """네이버 키워드 ID로 키워드 이름을 가져옵니다."""
    stmt = select(models.NaverKeyword).where(
        models.NaverKeyword.naver_keyword_id == naver_keyword_id
    )
    naver_keyword = db.execute(stmt).scalar()
    return naver_keyword.keyword

이 간단한 함수가 모든 문제의 원인일거라곤 생각못했다. 이 함수가 호출되는 밖에 결과가 없을 경우에 대한 에러처리는 해놨다.

1
2
3
4
5
6
...

res = get_keyword_name_by_naver_keyword_id(naver_keyword_id: "123", db=db)
if not res:
	continue

이게 왜 작동을 안하나 했는데.. 애초에 저 함수 내에서 에서 naver_keyword 가 None 일경우 NoneType은 ‘keyword’라는 attribute가 없다는 오류가 발생해버린다.. 내가 작성한 에러처리가 발생하기 전에 에러로 종료되버린다는 이야기다. 아오 에러시치.

개발자답게 노동하기

수백개에 달하는 템플릿을 추가해야 한다. 우선 현재는 128개를 추가해야하는데.. 각각의 한글이름, 스프레드시트 gid 값, 시트파일 이름을 입력해야 한다. 나중에는 내 업무가 아닐 것 같기도하고.. 해서 그냥 하나씩 추가하는 함수를 만들어놨다. 그럼 이 함수에 파라미터를 128번 입력해야 하는데.. ‘나는 pgadmin4 가 있으니까 낄낄’ 이라는 얄팍한 생각에 열심히 입력하고 있었다. 33번째를 입력할 때쯤 “이게 과연 개발자라는 인간의 행동이 맞는가?” 라는 철학적인 생각이 들었다. 컴퓨터 프로그램을 만드는 사람이 컴퓨터로 단순노동이라니.

스프레드시트 링크만 넣고, 추가하지 않을 gid만 설정해주면 알아서 추가해주는 함수를 만들었다. 편-안

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
@router.post("/spreadsheet")
def _get_data_from_spreadsheet(
    data: CreateTemplateBatchInput,
    service_id: str = Header(...),
    db: Session = Depends(get_db),
):
    """
    /spreadsheet/test 가 올바른 데이터를 추출하는지 확인하고 실행해주세요.
    템플릿들이 들어있는 스프레드시트에서 데이터를 가져와 템플릿을 데이터베이스에 저장합니다.
    템플릿 제목은 로우데이터와 템플릿 이름이 "-"로 구분되어 있어야 합니다.(ex. "NSA_T-파워링크 캠페인별 검색어 그래프")

    Args:

    | key | value | description |
    | --- | --- | --- |
    | platform | ImportPlatform | 데이터 수집 플랫폼 |
    | raw_data_type | RawDataFormat | 로우데이터 형식 |
    | url | str | 스프레드시트 URL |
    | exclude_gids | List[str] | 제외할 GID 리스트(리포트 설명, 로우데이터가 들어있는 시트 등) |
    """

    match = re.search(r"/d/([a-zA-Z0-9-_]+)", data.url)
    spreadsheet_id = match.group(1) if match else None

    if not spreadsheet_id:
        return False

    google_drive = GoogleDrive(service_id=service_id, db=db)
    sheets = google_drive.get_sheetfile_data(spreadsheet_id=spreadsheet_id)
    highest_version = get_report_template_highest_version(db=db)
    for i in sheets:
        sheetfile_gid = str(i.get("properties").get("sheetId"))
        sheetfile_name = str(i.get("properties").get("title"))

        if sheetfile_gid in data.exclude_gids:
            continue
        korean_name = list(sheetfile_name.split("-"))[1]

        new_template = models.ReportTemplate(
            platform=data.platform,
            raw_data_type=data.raw_data_type,
            korean_name=korean_name,
            spreadsheet_id=spreadsheet_id,
            sheetfile_gid=sheetfile_gid,
            sheetfile_name=sheetfile_name,
            version=highest_version + 1,
        )
        db.add(new_template)

    db.commit()
    return True

회고

홍익인간의 정신으로 기능을 만들자.