카카오 페이(2)

MeteorLee·2023년 6월 8일
0

Team 프로젝트

목록 보기
3/4
post-thumbnail
post-custom-banner

구현

지난 글에 이어서 카카오 페이 결제 시스템을 구현했던 과정을 적으려고 한다. 카카오 페이 결제 시스템에 관하여 집중적으로 다루기에 코드에 빈 부분이 있을 수 있다.

✅ 결제 흐름 이해하기

개발하는 과정과 결제 흐름을 섞어서 글을 적기에는 나의 글쓰기 능력이 너무나도 부족했기에 결제 흐름을 먼저 작성하고 코드를 적고 맞춰서 설명하고자 한다.

카카오 개발자 문서를 읽으며 개발을 진행하며 완성한 결제 시스템의 시퀀스 다이어그램이다. 유저의 요청을 받아 프런트 엔드 - 백엔드 - DB - 카카오 서버에서 결제를 주고 받는 과정들을 그리고 있다. 이제까지의 단순한 흐름이 아닌 카카오 서버에서 요구하는 요구사항들을 만족시키면서 흐름을 진행했다.

1. 유저의 요청

유저가 프런트 엔드에 카카오 결제를 요청한다. 주문 페이지의 카카오 페이 결제 버튼을 통해서 프런트에 요청을 보낸다.

2. 프런트 - 백엔드 결제 준비 요청

프런트에서 카카오로 결제를 진행할 경우 결제에 필요한 정보를 JSON, POST로 백엔드에서 보낸다. 필요한 내용은 주문 번호, 금액 등이 있다.

3. 백엔드에서 검증

백엔드에서 프런트에서 보낸 정보들 중에서 주문 번호를 이용하여 DB의 Orders(주문) 테이블에 접근하여 DB에 저장된 주문 정보의 금액과 프런트에서 요청받은 금액을 비교하여 검증한다.

4. 카카오 페이에 결제 준비 요청을 보낸다.

RestTemplate을 이용하여 카카오 페이 결제 준비 요청을 보낸다. POST방식 Content-type과 Authoization을 헤더에 헤더에 붙이고 MultiValueMap을 통해 카카오 문서에서 요구하는 요구 사항들(주문 번호, 금액, 상품명, approval_url ...) 등을 카카오 서버 url("https://kapi.kakao.com/v1/payment/ready")로 보낸다.

5. 카카오 페이에서 받은 정보를 저장, 프런트로 보낸다.

준비 요청이 성공적으로 완료된다면 카카오 서버에서 제공해 주는 정보를 받는다. 만약 요청이 성공한다면 주문 정보의 상태를 '승인 대기'로 변경하여 DB의 주문 테이블에 저장한다.

이 후 프런트에서 카카오 서버로 요청을 보낼 url(next_redirect_pc_url)을 프런트 서버로 보낸다.

6. 프런트와 유저 사이의 결제 진행

카카오 페이를 진행할 경우 보여지는 QR코드를 보여주는 화면을 보여주거나 모바일의 경우 카카오 톡을 받아 비밀번호를 입력하여 결제 완료를 누른다. 사실 이 부분은 시퀀스에 포함되지 않았는데 프런트 측에서 구현하기 나름이라 생각하여 생략하였다.

7. 프런트에서 결제

프런트와 유저 카카오 사이에서 결제가 완료된다면 카카오 서버에서 내가 결제 준비 요청에서 제공했던 approval_url, 즉 결제 성공 시 url로 Redirect GET 요청을 보내게 된다. 백엔드 서버에서 이 GET 요청을 받는데 카카오 서버에서 GET 요청을 보낼 때 pgToken을 쿼리 스트링으로 제공해 준다. 이 pgToken이 있어야만 결제 승인 요청을 할 수 있다.

8. 백엔드에서 DB에 접근하여 주문 정보를 가져옴

백엔드에서 카카오 서버에서 보내준 요청을 받는데 pgToken과 주문 번호를 받는다. 그리고 이 주문 번호를 이용하여 DB의 주문 테이블에 접근하여 결제 승인 요청에 필요한 정보(가맹점 주문 번호, 결제 고유 주문 번호, 가맹점 회원 id) 등을 받아온다.

9. 카카오 페이 승인 요청

다시 RestTemplate을 통해 카카오 문서에서 요구하는 사항들(POST, 헤더, 매개 변수) 등을 지켜 결제 승인 요청을 보낸다. 이때 필요한 정보 중 pgToken이 중요하다. 이 pgToken을 통해 카카오 서버에서 우리의 요청을 검증할 수 있다.

10. 카카오 페이 승인 응답

카카오 승인 요청이 성공하면 문서에 적힌 응답 정보들(결제 금액, 카드 정보, 상품 정보) 등을 받아온다. 이 중에서 서버에서 저장할 필요가 있는 정보들을 DB에 접근하여 저장한다. 또한 결제가 완료 되었으니 주문의 상태를 결제 완료로 변경하여 DB의 주문 테이블에 저장한다.

11. 결제 완료

백엔드에서 결제가 완료되었음을 프런트로 알리고 프런트에서 유저에게 결제가 완료 화면으로 이동시킴으로써 카카오 페이를 통한 결제를 마무리 한다.

코드

KakaoPaymentController

@RestController
@RequiredArgsConstructor
@RequestMapping("/account/pay/kakao")
public class KakaoPaymentController {

    private final KakaoPayService kakaoPayService;

    /**
     * 결제 준비 요청
     *
     * @return
     */
    @PostMapping("/ready")
    public ResponseEntity<KakaoReadyResponse> readyToKakaoPay(@RequestBody @Valid KakaoPayRequsetDTO requsetDTO) {

        KakaoReadyResponse response = kakaoPayService.kakaoPayReady(requsetDTO);

        return new ResponseEntity<>(response, HttpStatus.OK);
    }

    /**
     * 결제 성공
     *
     * @param pgToken
     * @return
     */
    @GetMapping("/success")
    public ResponseEntity<ResponseDTO> afterPayRequest(@RequestParam("pg_token") String pgToken,
                                                       @RequestParam("partner_order_id") String partner_order_id) {

        kakaoPayService.approveResponse(pgToken, partner_order_id);

        return new ResponseEntity<>(new ResponseDTO("200","success"), HttpStatus.OK);
    }

    /**
     * 결제 취소
     *
     * @return
     */
    @GetMapping("/cancel")
    public ResponseEntity<ResponseDTO> cancel() {

        return new ResponseEntity<>(new ResponseDTO("400","fail"), HttpStatus.OK);
    }

    /**
     * 결제 실패
     *
     * @return
     */
    @GetMapping("/fail")
    public ResponseEntity<ResponseDTO> fail() {

        return new ResponseEntity<>(new ResponseDTO("400","fail"), HttpStatus.OK);
    }

    /**
     * 결제 환불
     *
     * @return
     */
    @PostMapping("/refund")
    public ResponseEntity<ResponseDTO> refund(@RequestBody @Valid KakaoCancelRequestDTO requestDTO, Principal principal) {

        kakaoPayService.kakaoCancel(requestDTO);

        return new ResponseEntity<>(new ResponseDTO("200", "success"), HttpStatus.OK);
    }


}

KakaoPayService

@Service
@RequiredArgsConstructor
@Transactional
public class KakaoPayService {

    private final OrderRepository orderRepository;

    static final String cid = "TC0ONETIME"; // 가맹점 테스트 코드
    static final String admin_Key = ADMIN 키; // ADMIN 키

    /**
     * 결제 요청
     * 
     * @param requsetDTO
     * @return
     */
    public KakaoReadyResponse kakaoPayReady(KakaoPayRequsetDTO requsetDTO) {

        // 카카오페이 요청 양식
        MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();

        String partner_order_id = requsetDTO.getPartner_order_id();
        Orders order = this.getOrdersByPartnerOrderId(partner_order_id);

        // 입력 받은 금액과 DB 금액이 비교
        int dbAmount = order.getTotalPrice();
        if (dbAmount != requsetDTO.getTotal_amount()) {
            throw new KakaoSinglePaymentReadyException();
        }

        // 서버와 주고 받을 정보
        parameters.add("cid", cid);
        parameters.add("partner_order_id", partner_order_id);
        parameters.add("partner_user_id", order.getUser().getEmail());
        parameters.add("total_amount", String.valueOf(order.getTotalPrice()));

        parameters.add("item_name", "아이템 이름");
        parameters.add("quantity", "5"); // 아이템 갯수

        // 부가세, 비과세 금액으로 현재는 0으로 설정
        parameters.add("tax_free_amount", "0"); // 상품 비과세 금액 일단 0으로 설정
//        parameters.add("vat_amount", "0"); // 상품 부가세 금액 필수 아님, 없으면 0 설정
        
        parameters.add("approval_url", "http://52.78.88.121:8080/account/pay/kakao/success?partner_order_id=" +
                partner_order_id); // 성공 시 redirect url, 주문 번호 쿼리 스트링 추가
        parameters.add("cancel_url", "http://52.78.88.121:8080/account/pay/kakao/cancel"); // 취소 시 redirect url
        parameters.add("fail_url", "http://52.78.88.121:8080/account/pay/kakao/fail"); // 실패 시 redirect url

        // 파라미터, 헤더
        HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(parameters, this.getHeaders());

        // 외부에 보낼 url
        RestTemplate restTemplate = new RestTemplate();

        KakaoReadyResponse response = null;
        try {
            response = restTemplate.postForObject(
                    "https://kapi.kakao.com/v1/payment/ready",
                    requestEntity,
                    KakaoReadyResponse.class);
        } catch (RestClientException e) {
            throw new KakaoSinglePaymentReadyException();
        }

        // pg사 주문 번호 db 저장
        order.setPgUid(response.getTid());
        orderRepository.save(order);

        return response;
    }

    /**
     * 결제 승인
     * 
     * @param pgToken
     * @param partner_order_id
     */
    public void approveResponse(String pgToken, String partner_order_id) {

        // 카카오 요청
        MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
        parameters.add("cid", cid);

        // DB 접근하여 주문 정보 가져오기
        Orders orders = this.getOrdersByPartnerOrderId(partner_order_id);

        parameters.add("tid", orders.getPgUid());

        parameters.add("partner_order_id", String.valueOf(orders.getNumber()));
        parameters.add("partner_user_id", orders.getUser().getEmail());
        parameters.add("pg_token", pgToken);

        // 파라미터, 헤더
        HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(parameters, this.getHeaders());

        // 외부에 보낼 url
        RestTemplate restTemplate = new RestTemplate();

        try {
            KakaoApproveResponse approveResponse = restTemplate.postForObject(
                    "https://kapi.kakao.com/v1/payment/approve",
                    requestEntity,
                    KakaoApproveResponse.class);
        } catch (RestClientException e) {
            throw new KakaoSinglePaymentApproveException();
        }

        // 주문 상태 DB 반영
        orders.setStatus(OrderStatus.PURCHASED);
        orderRepository.save(orders);


    }

    /**
     * 결제 환불
     * 
     * @param requestDTO
     */
    public void kakaoCancel(KakaoCancelRequestDTO requestDTO) {

        // DB 접근하여 주문 정보 가져오기
        Orders orders = this.getOrdersByPartnerOrderId(requestDTO.getPartner_order_id());

        // 카카오페이 요청
        MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
        parameters.add("cid", cid);
        parameters.add("tid", orders.getPgUid());

        int cancelAmount = requestDTO.getCancel_amount();
        int totalAmount = orders.getTotalPrice();
        if (cancelAmount > totalAmount) {
            throw new KakaoRefundVerificationAmountException();
        }

        parameters.add("cancel_amount", String.valueOf(requestDTO.getCancel_amount()));

        parameters.add("cancel_tax_free_amount", "0"); // 환불 비과세 금액 일단 0으로 설정
        parameters.add("cancel_vat_amount", "0"); // 환불 부가세 금액 일단 0으로 설정

        // 파라미터, 헤더
        HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(parameters, this.getHeaders());

        // 외부에 보낼 url
        RestTemplate restTemplate = new RestTemplate();

        try {
            KakaoCancelResponse cancelResponse = restTemplate.postForObject(
                    "https://kapi.kakao.com/v1/payment/cancel",
                    requestEntity,
                    KakaoCancelResponse.class);
        } catch (RestClientException e) {
            throw new KakaoRefundException();
        }

        // 환불 금액 DB 반영
        orders.setTotalPrice(totalAmount - cancelAmount);
        orderRepository.save(orders);
    }

    /**
     * 카카오 요청 header 생성
     *
     * @return
     */
    private HttpHeaders getHeaders() {
        HttpHeaders httpHeaders = new HttpHeaders();

        String auth = "KakaoAK " + admin_Key;

        httpHeaders.set("Authorization", auth);
        httpHeaders.set("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

        return httpHeaders;
    }


    /**
     * 주문 번호를 이용하여 DB의 주문 테이블 정보 얻기
     * 
     * @param partner_order_id
     * @return
     */
    private Orders getOrdersByPartnerOrderId(String partner_order_id) {
        // DB 접근하여 주문 정보 가져오기

        Optional<Orders> result = orderRepository.findByNumber(partner_order_id);
        return result.orElseThrow(KakaoDBConnectionException::new);

    }
}

환불

환불은 하나의 요청과 응답으로 이루어지는 로직으로 유저 - 프런트 - 백엔드(DB) - 카카오의 단순한 흐름을 가졌다. 따라서 결제 승인 또는 결제 준비의 하나의 요청으로 간단하게 구현할 수 있었다. 물론 결제 와 관련된 부분이기에 DB에서 검증하는 로직을 가진다.

생각하고 고민한 흔적들

흐름을 대략적으로 이해한 상황에서 구현을 진행하면서 여러가지 문제점을 겪었다. 내가 부족해서 겪은 문제도 있고 현업에서는 어떻게 했을까? 라는 생각에서 비롯된 문제도 있고 문서를 제대로 읽지 않아서 겪은 문제도 있다. 이런 문제들을 해결하는 과정을 적고자 한다.

결제 과정에서 DB 접근은 어떻게?

사실 고민하고 고민해도 해결하기 어려운 문제라 멘토님의 도움을 받아 해결한 부분이다. 결제라는 것은 실제 돈이 오고가는 중요한 문제이다. 거기다가 주고 받는 정보 또한 민감한 정보가 많을 수 밖에 없다고 생각한다. 따라서 결제의 과정에서 주문의 상태가 변하는 것을 어떻게 DB에 반영할 지 많은 고민을 하였다.

이에대해 멘토님이 현업에서는 "모든 결제 상태를 DB에 저장한다"는 답변을 받았다. 나도 결제는 굉장히 중요한 부분이기에 모든 과정에서 DB에 저장하는 방식으로 안전성을 올리는 것을 생각했으나 DB에 접근하는 횟수가 늘어날 수록 성능이 저하되기에 고민했던 부분이다.

하지만 쇼핑몰 웹사이트에서 가장 중요한 부분을 차지하는 결제 시스템에서는 성능이 조금 저하되더라도 모든 정보를 DB에 저장하는 방식을 취하는 것이 웹사이트의 성격에 맞는 개발이라는 말을 들었다. 나는 개발 공부를 하고 프로젝트를 진행하며 DB에 접근하거나 throw를 통해 예외를 발생시키는 코드를 작성할 때마다 성능이 떨어지는 것을 걱정했다. 하지만 성능을 생각한 코딩도 중요하지만 프로젝트에 성격에 맞는 코딩이 더욱 중요한 것이다.

카카오 페이 결제 과정에서도 결제에 관련된 로직이 진행되는 것을 주문 테이블에 계속해서 반영하여 프로젝트 성격에 맞는 코딩을 진행하여 코드를 작성했다. 물론 DB에 접근하는 횟수가 많아 결제 기능의 속도가 조금 느려졌지만 높은 안정성을 가지게 되었다고 생각한다.

결제, 환불 에서 오류가 발생하면?

사실 오류가 발생하는 것은 당연히 고려해야 했지만 실제 돈과 관련되었기에 신중하게 고려할 필요가 있었다. 단순하게 카카오 서버에 접근, 백엔드에서 에러, DB 접근하는 상황에서의 문제 등은 결제가 완벽하게 끝나기 전까지 금액이 실제 결제 되지 않기에 문제가 되지는 않았다.

중요한 것은 서버의 DB의 금액과 실제 유저의 금액에서 차이가 생길 경우의 문제였다.
예를 들면 실제 유저가 카카오 페이를 통한 결제가 완료되어 금액이 빠져나갔지만 DB에서는 결제가 완료되지 않은 상황이다.

이 부분도 멘토님에게 질문을 했는데 사실 이런 오류의 경우에는 오류가 발생한 로그를 확인하여 개별적인 작업을 통해서 처리한다고 들었다. 이 과정에서 유지/보수 팀에서 작업을 한다고 한다.

결제 오류의 다양화

처음 코드를 작성했을 때 오류가 발생하면 전부 다 throw new PaymentException()으로 처리했다. 하지만 결제, 환불에서 발생하는 오류는 개별 처리가 필요하고 DB접근 오류도 개별 처리가 필요하는 등 결제 과정에서 발생하는 오류들을 세분화 할 필요가 있었다. 따라서 결제 관련 오류들을 세분화 하고 상속을 이용하여 Advice에서 하나로 처리하게 만들었다.

  • ControllerAdvice
@org.springframework.web.bind.annotation.ControllerAdvice
public class ControllerAdvice {

    ...

    /**
     * 결제 오류 처리
     *
     * @return
     */
    @ExceptionHandler({IamportPaymentException.class, KakaoPaymentException.class})
    public ResponseEntity<ResponseDTO> PaymentExceptionHandler() {

        return new ResponseEntity<>(new ResponseDTO("400","fail"), HttpStatus.OK);
    }
}

결제 승인에 어떻게 주문 번호를 담을 수 있을까?

사실 문서에 적혀있기는 하나 조금 더 강조해서 적어줬으면 좋았다고 생각을 많이한 부분이다. 해결 하기 위해 들었던 수고에 비해 내용은 정말 쉬운 부분이었다.

유저가 카카오 결제를 진행한 경우 카카오 서버에서 백엔드 서버로 pgToken을 쿼리 스트링으로 포함한 GET 요청으로 Redirect 요청을 보낸다. 이 과정에서 카카오 서버에서 요청을 보내는 것이기에 내가 쿼리 스트링에 정보를 넣을 수가 없었다. 거기다가 Http 요청이기에 이전 요청의 정보를 이용할 수도 없었다. 따라서 카카오 서버에서 백엔드 서버로 요청을 보낼 때 쿼리 스트링에 주문 번호 정보를 더해서 보내주어야 했다.

내가 요청을 하는 것이 아닌 카카오에서 요청하는 것이기에 세션을 이용하는 방안을 생각했으나 결제가 동시에 여러 건이 진행될 경우 문제가 생긴다고 생각하여 문제가 된다고 생각했다. 그렇다고 프런트에서 요청을 보내주는 방안을 생각하기에도 유저와 카카오 결제를 완료한 정보를 다시 프런트로 보내는 과정이 굉장히 불필요하고 pgToken이 탈취될 가능성도 있기에 보안상에서 큰 문제가 있었다.

사실 카카오 문서에 답은 있었다.

Step 4.결제 승인
결제 대기 단계에서 결제 요청에 성공했다면 결제 승인 API를 호출합니다. 이때 결제 승인 요청을 인증하는 pg_token을 전달해야 합니다. pg_token은 사용자가 결제 수단을 선택하고 결제 버튼을 눌러 approval_url로 리다이렉트될 때, 리다이렉트 요청의 approval_url에 포함된 query string으로 전달 받습니다.

저 approval_url에 쿼리 스트링을 추가하면 카카오 서버에서 Redirect할 때 쿼리 스트링을 포함해서 요청을 보내준다는 의미였다. 이 문제를 해결하기 위해 위에서 언급한 세션, 프런트 측 등 다양하게 시도해 보다가 구글링을 통해서 답을 알게 되었다.

https://developers.kakaopay.com/forum/t/topic/177 에서 답을 찾을 수 있었다.

  • KakaoPayService
...

parameters.add("approval_url", "http://52.78.88.121:8080/account/pay/kakao/success?partner_order_id=" +
                partner_order_id); // 성공 시 redirect url, 주문 번호 쿼리 스트링 추가
                
...

느낀점

결제 흐름 이해하기

지금까지 개발을 하며 만났던 가장 큰 고비였다고 생각한다. 카카오 서버로 보내는 단일 요청 조차 제대로 이해하지 못한 상황에서 결제 시스템의 큰 흐름을 이해하기까지의 과정은 쉽지 않았다. 거기다가 DB에 어떤 정보를 저장하고 정보르 주고 받는 타이밍과 횟수 등 또한 고려하다 보니 코딩 한 줄마다 '여기 이렇게 쓰면 나중에 DB를 앞에서 한다고 하면 에러가 날 것이고 뒤에서 DB에 접근하면 이 코드가 조금 의미가 없어지는데 어쩌지?'등의 생각을 많이 했다.

이런 상황에서 일단 해보자라는 생각으로 접근하기 보다는 하나씩 아주 잘게 하나씩 쪼개서 접근하기로 했다. 거기다가 코드를 작성하는 것이 아닌 요청과 응답의 흐름을 생각하여 결제 시스템의 목적에 맞는 흐름을 먼저 만든다는 생각으로 접근했다. 그렇게 코드를 먼저 작성하는 것이 아닌 큰 그림을 먼저 만들게 되었고 이를 바탕으로 코드를 작성하게 되었다.

이렇게 나만의 청사진을 그리고 코딩을 시작했다고 하더라도 문제점은 많았다. 청사진을 여러 번 수정하고 멘토님에게 질문을 던져가며 흐름을 바꾸며 개발을 진행했다. 처음 생각한 청사진과 다르게 변경되었지만 결제 시스템에 맞는 개발을 진행했다고 생각한다.(물론 아직 고쳐야할 점이나 부족한 점이 많은 것 같다.)

결제 시스템을 이해, 즉 클라이언트가 요구하는 요구 사항에 맞는 목적성을 가진 개발을 중요시하여 이에 맞는 청사진을 그리고 코딩을 진행하는 경험을 한 것이 정말 값진 일이었다고 생각한다.

구글링의 한계

이번 결제 기능을 구현하면서 절실하게 느낀 점이 구글링으 한계를 절실하게 느꼈다. 위의 겪었던 문제들을 해결하면서 결제 기능에 대한 구체적인 구현을 다룬 글을 발견하기 매우 힘들었다. 대다수의 글들이 카카오 페이 서버에 한 번의 요청, 결제 준비 요청 한 건을 다루는 내용이 대부분이 었다.

유저, 프런트 엔드, 백엔드, DB, 카카오 서버의 흐름을 나타낸 글도 찾아볼 수 없었다. 사실 수박 겉핥기 같은 내용이 많다는 생각을 많이 하게 되었다. 비슷한 내용도 정말 많았다. 물론 도움이 되지 않았다면 거짓말이겠지만 그 이상의 도움은 받을 수 없었다.

확실히 사람들이 많이 다루는 내용은 정말 많지만 다루는 사람이 적어질수록 정보의 양이 급감하는 것을 체감했다. 내가 지금까지 공부하는 대부분의 내용들은 많은 사람이 경험한 내용이었지만 앞으로 내가 공부하고 개발하는 과정은 많은 사람들이 경험하지 못한 것들도 많을 거라고 생각한다. 단순한 구글링과 블로그에서 벗어나 공식 문서, 깊이 있는 책을 가까이 하는 습관이 더욱 필요할 것 같다.

클론 코딩(강의, 책)

위의 구글링의 한계와는 조금 상충되는 말일지는 몰라도 어려운 개발을 경험하는 과정에서 이제까지 해오던 클론 코딩이 상당히 도움이 되었던 것을 많이 느꼈다. 연습할 때 정말 자연스럽게 될 정도는 되어야 실전에서 해볼 수 있다.는 말이 있듯이 내가 강의를 많이 듣고 책을 많이 보고 코딩을 많이 해왔던 일들이 값지게 느껴졌다. '그냥 자연스럽게 나온다'라는 생각이 조금 들었다. 반대로 내가 조금 많이 연습해보지 못했던 내용들은 손에서 자연스럽게 나가지 않고 찾아보고 다시 공부해야했다.

말을 적어보니 당연히 많이 경험한 것을 실전에서 사용한다는 것이지 않냐라고 할 수 있겠지만, 내가 말하는 것은 많이 코딩해본 것, 즉 직접 자판기에 두들긴 횟수에 비례한다는 생각을 많이 했다.

내가 알아도 코드로 작성하는 것은 별개다!

지식의 양이 비슷한 두가지 구현에서 많이 작성해본 코드는 코드 작성도 쉽고 이 코드를 어떻게 발전시킬 수 있을 지 고민하고 기존 코드들과의 연관 관계도 바로 반영하여 좋은 코드로 만들었다. 하지만 알고 있는 내용일 지라도 코드로 작성해보지 않았던 지식은 정말 기본적인 코드로 밖에 구현하지 못했다. 객체지향적이지도 않고 함수형을 사용하여 간편화 하지도 못하고 기존 코드들과 어울리지 못하는 코드들을 만들어 낼 뿐이었다.

'백문이 불여일타' 많이 쳐본 코드만이 내가 사용할 수 있다는 생각을 많이 하게 되었다.

마무리

결제를 담당한다고 지원했을 때는 패기롭게 지원했지만 결제 시스템을 구현하기 위해 노력하는 순간 순간 정말 포기하고 싶다는 생각을 많이 했었다. 이게 왜 이런거지!!!! 라는 절규를 매일 같이 하고 싶었다. 그래도 하나의 깨달음을 얻고 이를 코드로 구현해낼 때의 짜릿함은 이제까지 경험하지 못했기에 후회하지 않는다. 오히려 과거의 나에게 용기를 내어줘서 고맙다고 하고 싶다.

나의 개발 인생의 앞에 많은 장애물이 있을 것이다.(물론 실력 낮은 나에게만 있는 장애물일지 몰라도...) 그래도 이번 경험을 소중히 하고 포기하지 않는 태도를 가지고 앞으로 나아갈 것이다.

profile
코딩 시작
post-custom-banner

1개의 댓글

comment-user-thumbnail
2023년 7월 7일

안녕하세요 호승님 ~ 잘 지내고 계실지 모르겠네요 ㅎㅎ 자기소개글 작성하다가 저희 회고록 스터디 관련 내용이 있으면 좋을거같아서 참고차 혹시 아직도 꾸준히 작성하고 계신가 궁금해서 들어와봤는데 .. 대단하시네요 👏🏻👏🏻 항상 응원합니다!

답글 달기