728x90

전역 값

회사 프로젝트에서 resources 디렉토리에 있는 설정파일은 application.properties를 사용한다.

그래서 여기에 값을 할당해주고 @Value를 가져다가 사용한다.

properties의 값을 가져올때는 @Value("${properties키 값}")으로 불러오기 때문에

이번에 내가 진행하게된 프로젝트 설정파일 application.yaml에도 똑같이 @Value가 적용될줄 알았다.

근데 적용하려고 보니까 에러가 나는것이다.

에러메세지부터 바로 보자.

에러메세지

Could not resolve placeholder 'application.open-api.adminKey' in value "${application.open-api.adminKey}"

라는 에러가 발생했다.

설정을 전부 그대로 두고 다시 구현해보니까 그냥 성공하게 되는 이유는 무엇일까 🤔

추후에 다시 알아봐야 할 것 같다.

멍청하기는 아래의 해결법에서 yaml의 키를 보면 admin-key인데

위에 오류메세지에서는 adminKey로 매핑을 시켰었다.

정말 한심하기 짝이없다. 👿👿👿👿👿👿

아무튼 이런 설정 방법도 있구나 라는것을 알아두면 되겠다.

해결법

Baeldung 설정

벨덩 문서에 가보면 yaml파일 설정에 대한 것을 설명해주었다.

이걸보고 나는 키값을 할당해주려고 아래와 같이 구현하였다.

application.yaml

open-api:
  admin-key: 키값
  authorization: 인증코드

YamlConfiguration.java

@Configuration
@EnableConfigurationProperties
@ConfigurationProperties(prefix = "open-api") // 기본이 되는 뿌리 키값 여기서는 open-api이다.
@Getter
@Setter //setter가 없으면 Setter가 없다고 에러를 뱉어준다.
public class YamlConfiguration {
    private String adminKey;
    private String authorization;
}

여기서 @ConfigurationProperties 라는 어노테이션을 사용하려면 gradle에서

annotationProcessor에 다음을 추가해주어야한다.

annotationProcessor (
    // ...
            'org.springframework.boot:spring-boot-configuration-processor'
    )

그리고 사용하는 객체에서는 의존성 주입을 받아서 사용하면 되겠다.

public class Foo {

    private final YamlConfiguration yamlConfiguration;

    public Foo(YamlConfiguration yamlConfiguration) {
        this.yamlConfiguration = yamlConfiguration;
    }

    public void test() {
        yamlConfiguration.getAdminKey();
        yamlConfiguration.getAuthorization();
    }

}

@ValueConfiguration을 설정하는방법으로 어떤게 더 편한지 가독성이 좋은지 잘 모르겠다.

상황에 따라 편한걸 사용하는게 맞는지 이거는 조금 더 협업을 해보면 답이 찾아지지 않을까 싶다.

728x90

'디버깅' 카테고리의 다른 글

Jenkins 에러  (0) 2022.08.11
AWS SNS 토큰 에러  (0) 2022.08.10
AbstractMessageConverter  (0) 2022.08.09
RequestParamMethodArgumentResolver  (0) 2022.08.09
728x90

HandlerMethodArgumentResolver

HandlerMethodArgumentResolver에 정의되어있는 자바독을 읽으면

주어진 요청의 컨텍스트에서 메소드 매개변수를 인수 값으로 해석하기 위한 전략 인터페이스라고 설명되어 있다.

HandlerMethodArgumentResolver에는

스크린샷 2021-11-29 오후 10 09 59

이렇게 두개의 메소드가 있는데 supportsParameter()로 메소드의 매개변수를 처리할 수 있는지 여부를 판단한다.

@RequestBody

@RequestBody 어노테이션을 읽으려면

HandlerMethodArgumentResolver
AbstractMessageConverterMethodArgumentResolver
AbstractMessageConverterMethodProcessor
RequestResponseBodyMethodProcessor

이순서로 확장되어있는 Resolver를 찾아보면 된다.

저번 포스팅에서 봤던 ArgumentResolver들을 포함한 RequestMappingHandlerAdapter 에서는

HandlerMethodArgumentResolverComposite를 주입받게 된다.

HandlerMethodArgumentResolverComposite에서도

맞는 ArgumentResolver를 찾아서 동작하게 하려고 하는데 27개의 리졸버중에

RequestResponseBodyMethodProcessor@RequestBody를 처리해준다.

스크린샷 2021-11-29 오후 10 24 32
public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
  @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(RequestBody.class);
    }
}

그리고 이제 이 객체를 바인딩 해야한다.

MessageConverter

HandlerMethodArgumentResolverComposite에서 메세지 컨버터를 10개중에 또 찾아내야 한다.

그거는 아래에서 설명하겠다.

스크린샷 2021-11-29 오후 10 31 17

HttpInputMessage에서 들어온 키값과 @RequestBody가 붙은 객체 인자들 값을 서로 비교해서

맞으면 매핑을 시켜주는 것 같다.

AbstractMessageConverterMethodArgumentResolver

AbstractMessageConverterMethodArgumentResolver에서 MediaType

메세지 컨버터를 맞는걸 찾는데 걸리는 컨버터는 바로 MappingJackson2HttpMessageConverter이다.

예상은 하고 있었지만 왜 저 클래스가 GenericHttpMessageConverter 인지는 아직 잘 모르겠다.

메소드중 위 기능을 하는 일부를 가져와보았다.

스크린샷 2021-11-29 오후 10 46 47

public abstract class AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver {
  @Nullable
  protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
      Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

    MediaType contentType;
    boolean noContentType = false;
    try {
      contentType = inputMessage.getHeaders().getContentType();
    }
    catch (InvalidMediaTypeException ex) {
      throw new HttpMediaTypeNotSupportedException(ex.getMessage());
    }
    if (contentType == null) {
      noContentType = true;
      contentType = MediaType.APPLICATION_OCTET_STREAM;
    }

    Class<?> contextClass = parameter.getContainingClass();
    Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
    if (targetClass == null) {
      ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
      targetClass = (Class<T>) resolvableType.resolve();
    }

    HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
    Object body = NO_VALUE;

    EmptyBodyCheckingHttpInputMessage message;
    try {
      message = new EmptyBodyCheckingHttpInputMessage(inputMessage);

      for (HttpMessageConverter<?> converter : this.messageConverters) { //여기에 아까 말했던 Converter가 10개 들어오게 된다.
        Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
        GenericHttpMessageConverter<?> genericConverter =
            (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
        if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
            (targetClass != null && converter.canRead(targetClass, contentType))) {
          if (message.hasBody()) { //여기가 참 조건이 성립하려면 위에서 GenericHttpMessageConverter 클래스 유형이어야 한다.
            HttpInputMessage msgToUse = //여기는 메소드 이름을 보면 body를 읽기 전,후 로 나뉘어 있어 aop동작을 하는것으로 짐작된다.
                getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
            body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
                ((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
            body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
          }
          else {
            body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
          }
          break;
        }
      }
    }
    catch (IOException ex) {
      throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);
    }

    if (body == NO_VALUE) {
      if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
          (noContentType && !message.hasBody())) {
        return null;
      }
      throw new HttpMediaTypeNotSupportedException(contentType,
          getSupportedMediaTypes(targetClass != null ? targetClass : Object.class));
    }

    MediaType selectedContentType = contentType;
    Object theBody = body;
    LogFormatUtils.traceDebug(logger, traceOn -> {
      String formatted = LogFormatUtils.formatValue(theBody, !traceOn);
      return "Read \"" + selectedContentType + "\" to [" + formatted + "]";
    });

    return body;
  }
}

이렇게 해서 커맨드 객체 @RequestBody가 붙은 HelloForm유형에 맞는

데이터들을 파싱해주고 처리를 해줄 수 있게 된다.

번외로 AbstractMessageConverterMethodProcessor의 구현체는

RequestResponseBodyMethodProcessor, HttpEntityMethodProcessor 이므로

새롭게 알게된 사실인데 @RequestBody를 붙이고 싶지 않다면 HttpEntity<T>RequestEntity<T>로 매개변수를 받아주면

HttpEntityMethodProcessor가 동작하고, 가공이 조금 덜 되었지만 그래도 그안에 커맨드 객체까지 담아오는 body 데이터를 받을 수 있다.

정리

내 수준으로는 아직 이것밖에 이해 못했지만 그래도 얼추 디버깅으로 바짝 쫓아갈 수는 있다고 생각한다.

예전엔 이걸 봐도 어떻게 돌아가는지 무지성으로 넘기기만 했지, 자세하게 들여다 볼 실력도 안됐었다.

근데 지금은 천천히 늦지만서도 찍어보면서 어떻게 흐름이 진행되는지는 감을 익히는 것 같다.

이 부분도 자바 공부를 더 하다보면 깊게 알 수 있게 되지 않을까 싶다.

HelloController.java

@RestController
@RequestMapping("/api/v1")
public class HelloController {

    @PostMapping("/hello")
    public ResponseEntity<?> hello(@RequestBody HelloForm helloForm) {
        return ResponseEntity.ok(Map.of("message", "success","data", helloForm));
    }
}

HelloControllerTest.java

@WebMvcTest(HelloController.class)
class HelloControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @Test
    void test() throws Exception {
        String ss = "{\"message\": \"hello\", \"name\": \"lsj\"}";
        mockMvc.perform(post("/api/v1/hello")
                .content(ss)
                .contentType(MediaType.APPLICATION_JSON_VALUE))
            .andDo(print())
            .andExpect(status().isOk());
    }
}

HelloForm.java

@Getter //없으면 406에러를 발생한다. 깂을 못읽어 주입을 못해주는것 같다.
@NoArgsConstructor
public class HelloForm {

    private String message;
    private String name;

    //없으면 커맨드 객체 주입 안됨
    public HelloForm(String message, String name) {
        this.message = message;
        this.name = name;
    }

}
728x90

'디버깅' 카테고리의 다른 글

AWS SNS 토큰 에러  (0) 2022.08.10
YAML 파일을 읽어보자  (0) 2022.08.09
RequestParamMethodArgumentResolver  (0) 2022.08.09
RequestMapping 동작  (0) 2022.08.09
728x90

@RequestParam을 처리해주는 아규먼트 리졸버 = RequestParamMethodArgumentResolver

RequestParamMethodArgumentResolver는 AbstractNamedValueMethodArgumentResolver를 상속한 콘크리트 클래스고, resolveArgument를 오버라이딩 하지 않았기 때문에 AbstractNamedValueMethodArgumentResolver.resolveArgument가 호출된다.

흐름도

스크린샷 2021-11-25 오후 9 14 30

일단 흐름도는 이러하다.

어제 살펴봤던 내용은 DispatcherServlet 이전의 처리내용이었다.

스프링 MVC에 대해서 교환대라고 할 수 있는 DispatcherServlet 이 클래스가

HandlerAdapter 등.. 조건에 부합하는 객체들을 찾아서 전달을 다 해준다.

@RequestParam 이 있을 때는 당연히 매핑이 되어서 가져올것은 알았지만

없을 때 생략해도 가져오는 이것은 동작이 어떻게 되는지 궁금했다.

코드부터 보자.

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello(String id, String name) {
        return id + name;
    }

    @GetMapping("/hello2")
    public String hello2(@RequestParam String id, String name) {
        return id + name;
    }

}

이러한 두개의 예시 hello, hello2를 만들었고 테스트 코드로 디버깅하는게 더 좋을것 같아서

테스트 코드로 디버깅을 진행했다.

@WebMvcTest(HelloController.class)
@AutoConfigureMockMvc
class HelloControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void hello() throws Exception {
        mockMvc.perform(get("/hello?id=lsj&name=홍길동"))
            .andDo(print())
            .andExpect(status().isOk());
    }

}

일단 돌리게 되었을 때 DispatcherServletRequestMappingHandlerAdapter를 호출하게 된다.

여기서 요청 객체를 처리하는줄 알았었는데

ArgumentResolver들 중에 해당 객체를 처리할 수 있는 Resolver 클래스를 찾는다.

스크린샷 2021-11-25 오후 9 25 03

RequestMappingHandlerAdapter의 일부 메소드인데 이 클래스가

부름을 받으면 바로 Resolver들을 추가해주는데

여기서 유심히 봐야하는 부분이 디버그로 파란줄 쳐진 부분과

//catch-all아래 2번째줄 이 두줄을 유심히 봐야하는데

여기서 내가 느낀것은 useDefaultResolution이 옵션이라고 생각하고 넘어갔다.

스크린샷 2021-11-25 오후 9 28 58

HandlerMethodArgumentResolverComposite

다음은 저 어댑터들을 가지고서 넘어온 파라미터를 처리해줄 수 있는 Resolver들을 찾는데

스크린샷 2021-11-25 오후 9 41 16

그것은 이 HandlerMethodArgumentResolverComposite에서 for문으로 찾아주게 되어있다 ❗️❗️❗️❗️

RequestParamMethodArgumentResolver

여기서 체크하는 로직이 위 이미지 RequestParamMethodArgumentResolver 이다.

보면 파라미터 어노테이션을 갖고 있는지 여부, 또는 @RequestPart를 갖고있는지 등등

분기로 판단해서 객체를 처리하려고 한다.

여기서 아까 보고 넘어갔다했던 useDefaultResolution 애가 분기문에 this.useDefaultResolution이 보이는데

이게 true라면 현재 가지고있는 매개변수의 타입대로 따라가서 값을 매핑시켜준다.

스크린샷 2021-11-25 오후 9 35 30

0번째 인자는 false, 25번째 인자는 true를 갖고있다.

정리를 해보자면

resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
상태값이 false이면 @RequestParam이 있는 경우 매개변수 생성

resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
상태값이 true이면 @RequestParam이 없는 경우 매개변수 생성을 하는 것

그래서 둘다 있으나 없으나 생성을 해준다.

근데 이제는 성능은 어떤게 좋냐고 묻는다면 명시적으로 @RequestParam을 붙인 객체는 0번째에서 바로

찾아서 매핑이 될것이다.

반대로 명시적으로 붙이지 않았다면?

매번 25번째에 있는 Resolver를 통해서 매핑시키게 될 것이다. 이게 단순 한개라면 모르겠지만,

여러 사용자 + 여러 스레드 + 파라미터의 갯수 세개의 조건이 셋중에 하나 또는 전부가 많아진다면

성능은 안좋아질게 훤히 보인다.

그래서 안붙여도 되지만 성능을 최적화 하려면 명시적으로 @RequestParam을 붙여주는것이 좋다.

정리

테코톡에서 디버그를 본 후에 깊게 한번 들어와서 공부를 해보려고 어제부터 탐색을 했다.

깊게 이렇게 들어와서 하나씩 보는것이 소스분석에도 도움이 되고 더 나아가서는

회사코드를 인계받을 때에도 이렇게 분석을 하면 핵심 로직을 빠르게 파악할 수 있을 것 같다.

그러면서 동시에 이렇게 정리까지 하니 머릿속에 많이 남아서 지식으로 가져가는 것도 좋은것 같다.

공부를 이렇게 했어야 됐는데 너무 늦은건가 싶기도 하지만 꾸준한게 답인것 같다. 🔥🔥🔥

728x90

'디버깅' 카테고리의 다른 글

AWS SNS 토큰 에러  (0) 2022.08.10
YAML 파일을 읽어보자  (0) 2022.08.09
AbstractMessageConverter  (0) 2022.08.09
RequestMapping 동작  (0) 2022.08.09
728x90

얼마만의 포스팅인지 모르겠다.

일단 바로 스타트 ❗❗❗

깃허브를 원래 올리는게 맞지만 회사 코드라서 따로 올리지는 못한다. 😥😥😥

테스트

@GetMapping에 대해서 어떻게 돌아가는지 궁금해서 무작정 실행을 시켜봤다.

image

지금 보이는 이미지는

톰캣에서 HTTP 메세지를 받아오는 구간이다.

그러니까 DispatcherServlet전에 수행되는 구간이다.

어떻게 저걸 담고있냐는

localhost:8081/swagger-ui.html 이라는 곳에서

크롬브라우저를 이용한 HTTP 통신을 하려고한다 라는 헤더를 추출한것이다.

image

여기서 api를 호출하고 GET방식으로 조회하는것 까지 확인했다.

오해했던 부분

처음에 나는 @GetMapping이 GET메소드를 만들어준다?

라고 생각했다.

HTTP 완벽 가이드를 읽으면서도 멍청하게 생각을 했다.

나는 바보다 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ

그런게 아니라 HTTP 메소드에서 헤더를 보고

이러이러한 요청이 있다. 부터 시작하는 것이 웹의 동작일텐데 간과하고 있었다.

과정

이렇게 쭉 지나오면 이제 FrameworkServlet을 만나게 된다.

image

이 클래스는 추상클래스로 되어있고

이걸 구현한게 바로 DispatcherServlet이다.

image

프레임워크 서블릿의 이 메소드를 지나면서

디스패처 서블릿이 이제 해당 요청에 대한 메소드를 탐색한다.

이 때 탐색하라고 핸들러에게 넘기는데

image

그 핸들러가 바로 RequestMappingHandlerAdapter 클래스이다.

얘가 @RequestMapping에 대한 모든 요청을 받아서 매칭을 해주고

핸들러 메소드들을 쭉 지나서 마지막에는 내가 구현한 API쪽으로 넘어오게 된다.

그렇게해서 해당하는 데이터들을 가져와서 클라이언트쪽으로 가공된 알맞는 데이터들을 보내주게 된다.

정리

이렇게 깊게 찾아보는게 기억에도 훨씬 잘남고 개념 확립이 착착 되는것 같아 좋다.

문득 낮에 보던 우아한 테코톡 디버깅을 보고 그리고 순간 궁금했어서 이렇게 찾아보게 된것 같다.

암만 주저리주저리 읊고 해봤자 모르겠고

그냥 코드를 보는게 답이라고 생각한다.

728x90

'디버깅' 카테고리의 다른 글

AWS SNS 토큰 에러  (0) 2022.08.10
YAML 파일을 읽어보자  (0) 2022.08.09
AbstractMessageConverter  (0) 2022.08.09
RequestParamMethodArgumentResolver  (0) 2022.08.09
728x90

OpenSSH

이 글을 쓰게된건 일단 회사에서 여유시간이 생기게 되면 공부하는 시간을 가져도 된다고 하셨었다.

그래서 이참에 사이드 프로젝트 하던것을 좀 ec2 서버에 올려놓으면서 RDS설정도 해야겠다 싶었다.

근데 키가 없으니까 또 접속하기가 매우 불편했다.

왜냐면, 웹으로 접속하는 법을 썼어야 했으니까.

키 파일이 없는데 언제 어디서나 접속하게끔 하고 싶었고

그렇게 적용하게된 OpenSSH이다.

설치

일단 시작은 다음과 같다.

sudo apt update
sudo apt-get install openssh-server

먼저 패키지 시스템을 업데이트 해주고, openssh-server를 설치해준다.

ssh를 설치하게 되면 자동으로 실행된다.

그리고 나서 현재 상태를 조회할 수 있다.

sudo systemctl status ssh
스크린샷 2021-11-11 오후 8 51 12

이렇게 상태를 보고 동작중인지 확인이 되었다.

만약에 동작중이 아니라면

sudo systemctl enable ssh
sudo systemctl stop ssh
sudo systemctl start ssh

sudo systemctl restart ssh

stop, start를 사용하던지

restart를 사용하던지 편한것을 사용하면 되겠다.

방화벽은 나는 ec2 서버의 ubuntu를 사용하였기 때문에

인바운드 규칙에서 22번을 열어주면 방화벽 설정은 따로 해줄 필요가 없었다.

그리고 이제

접속할 쪽에서 openssh-client를 받아준다.

윈도우는 여기서 다루지 않겠다. 다른곳 가면 잘 나온다.

나는 WSL2를 사용해서

sudo apt-get install openssh-client

를 해줘서 패키지를 다운받았다. 그리고 나서 접속하는 명령어는

기존 명령어부터 먼저 보자면,

ssh -i "private key.pem" username@ip_address

방식이었고 pem파일이 없으면 실행이 안됐다.

설정법

일단 설치는 다 진행했고 다시 ec2서버로 가서

vi /etc/ssh/sshd_config 파일을 열어준다.

여기서 openssh-server에 대한 설정을 해줄 수가 있는데

전부 주석으로 처음에 막혀있다.

스크린샷 2021-11-11 오후 8 58 52

이렇게 주석되어 있는 소스들을 풀어주었다.

포트하고 어느 주소만 허용할 것인지

나는 언제 어디서나 사용할 수 있게끔 ip를 다 열어주었다. 사내에서는 이렇게 하면 안될 것이다.

막혔던 부분

아니 나는 당연히 여기서 무조건 되겠지 하고 걍했다.

남들 하는것처럼 비밀번호 나오겠지 뭐

또는 아? 인증화면에서 yes 해서 known_hosts에 등록되겠지 했다.

ㅋㅋㅋㅋㅋㅋㅋㅋ 미친짓이었다.🔥

해결책

🤔 왜 안됐을까?

아니 비밀번호를 설정만 해주면 뭐하나

일단 비밀번호 설정을 모르는 사람들을 위해 명령어 한줄 치고 가겠다.

나는 default username인 ubuntu를 썼다. 그리고 ubuntu 계정으로 들어와있다.

스크린샷 2021-11-11 오후 9 08 45

그렇기때문에

뭐 모르는상태에서 변경해야 될 수도 있을때는 sudo를 붙이면 되고,

물론 root가 아닌 얘기다!!!

sudo passwd username 으로 비밀번호를 설정해주면 된다.

여기까진 당연한것이고...

아까 설정했던 부분인 vi /etc/ssh/sshd_config로 다시 가보자

아래로 내려보면서 찾아보면

스크린샷 2021-11-11 오후 9 13 13

PasswordAuthentication 설정이 있다.

비밀번호 인증 ? yes 를 해야 당연히 적용이되지 바보같은것..

그래서 이제는 이 변경 설정을 적용해줘야 하기 때문에

위에서 봤던 명령어로 다시 껐다가 켜주면 정상적으로 로그인이 된다!!!

스크린샷 2021-11-11 오후 9 14 57

이렇게 ssh 접속으로 비밀번호까지 쳐주게 되면?

스크린샷 2021-11-11 오후 9 17 14

잘 나오게 된다. 아주 편리하고 좋은 것 같다.

이제 여기에 여러가지 설정들을 할것이다❗️❗️❗️❗️❗️ 🔥🔥🔥🔥

728x90

'클라우드' 카테고리의 다른 글

Docker compose  (0) 2022.08.07
[Docker] 도커란  (0) 2022.08.05
728x90

모든 코드는 깃허브에 있음을 알린다.

ALU

열심히 한 끝에 결국 2장의 마지막 끝판왕 ALU까지 오게 되었다.

아 이 ALU를 하려고 1장부터 몇번을 다시 봤는지 모르겠고,

기초를 차곡차곡 쌓는게 중요하다고 다시한번 느낀 그런 날이다.

이전 내용은 2장 불연산 앞부분 회고를 참고하면 되겠다.

일단 ALU는 산술논리연산장치 로서

덧셈, 뺄셈 같은 두 숫자의 산술연산과 배타적 논리합, 논리곱,

논리합 같은 논리연산을 계산하는 디지털 회로이다.

ALU는 CPU의 내부 구성 요소중 하나이며, 실제적인 연산을 담당하는 하드웨어 모듈이다.

컴퓨터시스템의 다른 부품들은 사실 ALU가 연산하기 위해 처리될

데이터를 가져오거나,

그 결과를 저장하기 위한 용도라고 해도 틀린 말이 아니다.

ALU는 구성이 산술, 그리고 논리로 되어있다.

산술

ALU는 덧셈만 있지 나눗셈, 곱셉은 존재하지 않는다.

곱셈은 그냥 * 5 해줄것을 5번 더해주는것으로 생각하고

나눗셈은 / 5 해주는 것을 5번 빼주는식으로 해주면 된다.

그래서 ALU는 덧셈으로 산술연산을 모두 처리할 수 있다.

논리

이 논리 연산으로 같은 값인지? 다른값인지 누가더 큰지 이런것을 비교하여 값을 받을 수가 있다.

회로도

조건식은

  • zx
    • if zx then x = 0
  • nx
    • if nx then x = !x
  • zy
    • if zy then y = 0
  • ny
    • if ny then y = !y
  • f
    • if f then out = x + y else out = x & y
  • no
    • if no then out = !out

이 셀렉터들의 조건이 있다.

그리고 아래는 회로도를 직접 그려본 것이다.

IMG_2742

처음에 위의 저 조건들은 당연히 이해를 했다.

그래서 아 이것도 쉽구나 하고 쭉 구현해보려고 했는데 문제가 있는 부분은

현재 그림으로 no 셀렉터 값을 가진 16비트 멀티플렉서 를 지나는 지점부터 이해가 잘 되지 않았다.

왜냐면 생각이 안들기 때문이었다. ㅋㅋㅋㅋㅋㅋ

그러니까 기초가 부족했던 탓이라고 생각했다. 😅

이제 출력값이 세개였는데

Output : 16비트 출력
zr : out = 0 일때 True 출력
ng : out < 0 일 때만 출력

이 세개가 왜 나와야하는지 그리고 저게 뭘 의미하는지 사실 잘 몰랐었다.

근데 2장을 앞부분으로 되돌아가면

16비트는 정상 출력을해주고

out이 0보다 작다는 소리는 맨 앞부분이 부호비트 라는 것을 생각해보면

아! 맨 앞 비트를 뽑아주면 되는구나!

까지 생각했다.

그래서 저 두개 부분은 금방 해결을 했다.

마지막 해결 부분

마지막 out = 0 일때 True 출력

이 부분은 사실 8개씩 나누어 두개의 Or8Way 를 사용하였다.

비트를 압축해야 하는데 자른 8개의 비트중에 하나라도 1이라면 그냥 1이 되게 되어있다.

그래서 no 라는 셀렉터가 값을 반전시키기 때문에 전부 0이 나올때만 1로 출력되게 해주어야 했다.

그래서 Or8Way로 비트를 2개로 압축한다. 이 결과를 다시 Or조건으로 넣으면

16비트 전부 0이 나오면 이제 그 0을 반대값으로 환산해서 모두가 0일때

참으로 나와주게끔 구현을 한 것이다.

아래는 nand2tetris의 코드이다.

CHIP ALU {
    IN  
        x[16], y[16],  // 16-bit 입력        
        zx,
        nx,
        zy,
        ny,
        f,
        no;

    OUT 
        out[16],
        zr,
        ng;

    PARTS:

    // zx, zy
    Mux16(a=x, b=false, sel=zx, out=o1);
    Mux16(a=y, b=false, sel=zy, out=o2);

    //nx
    Not16(in=o1, out=noto1);
    Mux16(a=o1, b=noto1, sel=nx, out=o3);

    //ny
    Not16(in=o2, out=noto2);
    Mux16(a=o2, b=noto2, sel=ny, out=o4);

    // f연산
    And16(a=o3, b=o4, out=andxy);
    Add16(a=o3, b=o4, out=addxy);
    //add or and 정의해놓고 멀티플렉서로 선택함
    Mux16(a=andxy, b=addxy, sel=f, out=result);

    //no연산
    Not16(in=result, out=notresult);

    //위와 같이 not 구현해놓고 not연산에 해당하는 값을 멀티플렉서로 선택함
    Mux16(a=result, b=notresult, sel=no, out=out, out[0..7]=front, out[8..15]=back, out[15]=ng);

    Or8Way(in=front, out=frontout);
    Or8Way(in=back, out=backout);
    Or(a=frontout, b=backout, out=orout);
    Not(in=orout, out=zr);

}

결론

드디어 끝을 보니까 너무 후련했다.

위의 코드를 HardwareSimulator를 실행해서 테스트 케이스를 통과할 때의 그 기분은 느낀 사람만 알 것이다.

확실히 손으로 그려보고 딱딱 만들어 가는 과정이 나는 더 잘 맞는것 같다.

근데 이 고통을 겪어서 그런가 감히 다음장에 나오는 플립플롭, 레지스터에 도전하고 싶다.

728x90

'CS' 카테고리의 다른 글

Cache  (0) 2022.08.09
불 연산 회고  (0) 2022.08.09
불 논리 정리 2  (0) 2022.08.09
불 논리 회고  (0) 2022.08.07
728x90

불 연산 회고

이 2장 정리를 진작에 했어야 했는데 시간이 나지를 않아서 지금 포스팅하게 되었다.

모든 코드는 깃허브에 있다.

일단 저번 회고 때 마지막으로 정리했던것은 카르노맵 이었다.

카르노맵으로 뭔가의 공식들을 도출할 수 있으니까 논리식을 간소화한 후에 회로도를 셋팅하는 정도는 이제 무리가 없었다.

이게 정말 다행이었고 여기서부터는 이제 입력 값이 3개이기 때문에 3변수 카르노맵을 통해

풀게 되었다.

일단 2진수의 덧셈을 생각해보면 2진수는 0과 1로 이루어져있다.

일반적인 10진수에서 덧셈을 할때를 보면 10일때는 올림처리를 한다.

2진수도 마찬가지다 도합 2가 되면 올림처리를 해주는 것이다.

그것을 Carry라고 부른다.

KakaoTalk_20211023_155551656

자 반가산기 이다. 반가산기는 덧셈을 수행하는 가산기이며 입력값이 두가지 이다.

그러니까 처음 계산할 때 대부분 반가산기를 사용할 것이다.

2진수로 1 + 1 = 0 이 성립한다.

이때 X = 1, Y = 1인것이고 Carry(올림)이 1이다 왜냐면 저 0은 더해서 2가 되어서

올림 처리를 해주고 남은 0이라서 합계가 0이 되는 것이다.

그래서 진리표대로 잘 나오는 것을 확인하였고 이것을 토대로 카르노맵을

Carry의 입장, Sum의 입장에서 하나씩 구현을 했다.

이 반가산기는 언제쓰나 했더니 자세히 보니까 한자리 덧셈만 할 때,

이 때만 수행해준다.

전가산기

KakaoTalk_20211023_155551179

이 전가산기가 나온이유가 바로 위에서 반가산기 덧셈 후에

그러니까 맨 처음 덧셈을 수행할 때만 이렇게 해주고

나머지는 올림처리한 입력값 까지 같이 계산을 해야되니까

이 전가산기를 통해 나머지 연산을 해주어야 한다.

16비트 가산기

그래서 이 16비트 가산기가 설명을 하자면

예를들어 1111 + 1010 이 두개를 더한다고 해보자

1 1 1 1

1 0 1 0

맨 오른쪽인

1과 0을 보면 이제 이 부분이 반가산기로 연산을 수행하는 것이다.

1 + 0 은 0이고 Carry 또한 발생하지 않았기 때문에 그대로 진행하고 그 다음부터는 올린 수를 고려해야 하기 때문에

3개 입력 -> 이전 Carry 0 + 1 + 1이 되는 것이다.

HalfAdder

FullAdder

...

를 수행하게 된다.

이렇게 반가산기, 전가산기 두개로 가산기에 대해서 알아보았다.

ALU라는 2장의 끝판왕이 존재하는데 저번 회고에서

유튜브 페이지에 들어가서 Computer Science 탭에 ALU설명을 듣고나니까 구현할 수 있을것만 같다.

토요일에 구현해보고 다시 회고를 작성해보겠다.

728x90

'CS' 카테고리의 다른 글

Cache  (0) 2022.08.09
ALU 구현  (0) 2022.08.09
불 논리 정리 2  (0) 2022.08.09
불 논리 회고  (0) 2022.08.07
728x90

2진수

2진수는 0과 1로만 이루어져있는 수이다.

1bit의 단위를 가진다.

bit란?

2진수의 기본 단위이다.

패리티 비트

패리티 비트는 전달하는 데이터의 오류를 검증해주는 비트이다.

문자열 내 1비트의 모든 숫자가 짝수 또는 홀수인지를 보증하기 위해 전송하고자 하는 데이터의 각 문자에 1 비트를 더하여 전송하는 방법으로

2가지 종류의 패리티 비트(홀수, 짝수)가 있다. 패리티 비트는 오류 검출 부호 에서 가장 간단한 형태로 쓰인다.

실질적으로 오류를 확인할 수 있는 비트이지, 그 오류에 대한 해결을 할 수는 없다.

보수

보수는 사전적 의미로 보충을 해주는 수 라고 정의되어 있다.

숫자 2의 10의 보수는 8이 되는 것이다.

논리 회로에서의 보수는 뺄셈 연산을 할 때, 좀 더 효과적으로 수행하며

논리적 조작의 용이성을 위해 사용한다.

  • 1의 보수
    • 2^n -1 자리수 최대값에서 해당하는 이진수 값을 빼주면 그것이 바로 1의 보수
  • 2의 보수
    • 2^n - 1자리수 최대값에서 해당하는 이진수 값을 빼준 후에 1을 더해주는 보수

KakaoTalk_Photo_2021-10-30-15-58-08

1의 보수 감산

  1. 1의 보수 감산은 주어진 수들을 일단 2진수로 변환한다.
  2. 음수로 되어있는 수는 1의 보수를 취해준다.
  3. 서로의 자릿수가 다르다면 자리수를 맞춰준다.
  4. 앞에 양수면 0, 음수면 1의 부호비트를 붙여준다.
  5. 덧셈을 진행한다.
  6. 진행한 후에 자리올림(Carry)가 발생하면 최하위 비트에 1을 더해준다.

그 후 맨앞의 부호비트는 떼어놓고 나머지 수들의 자릿수만큼의 경우의 수(절대값)은

2^n-1에서 떼어놓은 나머지 수를 빼주고 10진수로 변환한다.

그리고 나서 부호비트를 표현해주면 그것이 보수감산하여 나온 값이 된다.

2의 보수 감산

2의보수는 1의보수 + 1 이기 때문에 1의보수 방법에서

보수로 변환을 해준 뒤에 + 1을 시켜준다.

마찬가지로 부호비트를 추가해주고 덧셈을 수행하면 된다.

그리고 나서 보수감산이 진행되고 나서 다시 +1을 해준다.

불대수 법칙

이전 포스팅에서도 정리를 했었는데, 다 알고있던건 수기로 작성했고

드모르간 정리는 저번 포스팅 때에 존재하였기 때문에,

제2 분배법칙을 따로 증명해보았다.

그게 바로 X + YZ = (X+Y)(Y+Z) 이런 법칙이다.

(X+Y)(X+Z)를 분배해서 일일이 나열하게 되면

XX + XY + XZ + YZ 가 되는데,

여기서 XX + XY를 X에 대해 묶어주면

X(1 + Y)가 된다.

기본법칙에서 1 + 변수는 1이되므로 생략이 된다.

그래서 저 식을 풀면 그냥 X가 된다.

정리를해보면 X + XZ + YZ 가 남게 되는데 여기서 또,

X에대해서 X + XZ를 묶어주게 되면

X(1 + Z)가 되어서 또다시 X가 된다.

그래서 최종적으로 보게 되면 X + YZ 가되어 두식이 같다는걸 증명했다.

이제 좀 탄력을 받아서 ALU 구현하는데에 도전해볼 것 같다는 확신이 든다.

유튜브 페이지에 들어가서 Computer Science

아주 좋은 강의가 순서대로 있었다.

논리게이트부터 CPU까지만 지금 들었지만 굉장히 이해하기 쉽고 재밌게 설명해서

보면서 감탄만 한것 같다.

728x90

'CS' 카테고리의 다른 글

ALU 구현  (0) 2022.08.09
불 연산 회고  (0) 2022.08.09
불 논리 회고  (0) 2022.08.07
HTTP  (0) 2022.08.07
728x90

업무 회고

3개월 좀 넘게 이제 지금의 회사를 다니는 중인데,

사실 정말 짧은기간이라고 봐도 무방하다.

근데 이 3개월동안 회사에서 했던건 정말 많았다.

왜냐면 내가 지금 혼자 백엔드를 맡아서 개발하고 있으니까 말이다.

전에 있던 백엔드분은 보지도 못했고 따로 인수인계 받은게 없었으니

기존의 기능에서 추가적으로 개발하는데 꽤나 애를 먹었었다.

인수인계 과정

그러니까 지금 백엔드 프로젝트 1개가 인계한 순서가

전 백엔드 근무자 → 프리랜서 → 나

이 순서로 인계가 되었기 때문에 뭐 제대로 받은게 없었다.

당연히 받은 코드도 이해하는데 꽤나 걸렸으며,

특히 테스트 코드 가 없었다....

어떤식으로 돌아가는지에 대한 이해를 전혀 할 수가 없어서 짜증이 났지만,

그래도 이게 또 기회랍시고 나는 넥스트스텝의 TDD 과정을 들은 것을 토대로 테스트를 작성했다.

테스트 코드

테스트 코드는 정말 작은것부터 시작을 했다.

누구한테 물어볼 그런것도 없었거니와, 추가적으로 알아야 하는것은 구글신 에게 의존하는 수 밖에 없었고, 그러면서 동시에 어떤 라이브러리에 대한 공식문서 보는것이 좀 당연해졌다.

지금 구조는 회사의 백엔드 기술력이 오로지 나 혼자가 일궈낸 것이 되는것이다.

처음 왔을 때부터 그랬으니까 뭔가 도입하거나 하려는 것도 내가 배워서 적용만 하면

되는것인데,

방향을 잡아줄 수 있는 사람이 없다는 것 이게 정말 아쉬운 부분이었다.

하지만 단위테스트를 적용해놓고나서 보니까 중복된 코드도 많이 보였고,

쓸모없는 로직, 불필요한 주석

추가적으로 어떤 변수에 대해 사용하지 않은데 향상된 for문, 람다 forEach가 섞여있는 모습들이 많았다.

무조건 분기해서 만든 긴 ifElse 조건문들...

와서 모두 디자인패턴에 대한 공부, TDD Clean Code with Java 12기 에 학습하면서

다 안좋게 보여서 전부 고쳤다.

테스트가 항상 통과되고 안전하다는 보증을 서줬기 때문에 믿고 막 고칠 수 있었던것 같다.

협업

협업에서도 아쉽지만 우리 회사에는 Git에 대해 좀 생소할 수도 있고, Jira, Slack 등등... 사용하지를 않아서 협업 질문에 대해서 다 협업은 하고싶다고들 얘기를 하셨다.

그렇기 때문에 Git Flow에 대해서 회의실에서 내가 발표를 하고 이 Flow대로 Git을 제대로 사용해보자 도입해보자 건의를 했다.

대답들은 전부 긍정적이셔서 이렇게 협업의 장을 내가 열었다(?) 고 봐도 될 것이다.

Jira에 대해서는 들어는 보셨지만, 사용해보지는 않았다고 하셔서 이전 직장에서의 사용했던 경험과, 그리고 지금 진행중인 스터디 에서 사용했던 거로 설명을 전부 했었다.

그리고 제일 중요했던 것...

스웨거 도입

여태 API문서 에 대한건 전부 엑셀파일로 백엔드 개발자 분이 직접 만들거나 또는 Postman에 올리거나 하는식으로 진행을 했다고 한다....

당시에 나는 이 방식이 정말 귀찮았다.

이거를 내가 API 개발함과 동시에 문서화를 하는게 낫다고 판단했다.

왜냐면 서버가 돌아가면서 그안에서 테스트를 해볼 수 있기 때문이었다.

그리고 구축을 해두었으니 별도로 따로 타이핑을 해서 문서화 해야하는 불편함을 좀 덜 수 있었기 때문이다.

그래서 Spring Rest DocsSwagger 2 중에 고민을 했지만,

디자인이 좀 알록달록하고 Spring Rest Docs보다 Swagger가 설정이 좀 더 편했으니

이게 좀 더 합리적이라고 생각했다..

프론트엔드 한분이 스웨거는 많이 보셨다고 한게 한번의 작용을 했다고도 볼 수 있다. ㅋㅋㅋㅋㅋㅋㅋ

여기까지 해서 GitSwagger, Jira까지 협업에 대한 밑바닥부터 조금 만들었다.

마이그레이션 경험

일단 Mybatis 기술을 사용한 것이 정말 많았다...

이 기술 쓰는 것 그 자체에 대한 고찰이 생겼었는데

그 이유는 바로 명색이 백엔드 그것도 자바 개발자인데,

Mybatis 안의 그 핵심 쿼리들, 그리고 그 안에 비즈니스 로직을 다 넣게 되는 것이다.

그래서 원하는 데이터만 딱딱 뽑아오게끔 거의 설계가 됐었다.

전직장도 동일하다(물론 iBatis였지만)

자바를 많이 보고 객체지향적인 고민을 하는것이 아니라,

데이터적인 생각, 항상 데이터만 이렇게 가져오면 된다에 심혈을 기울여서

쿼리에서 데이터만 받아서 처리해주면 된다. 라는 생각만 갖게 됐던 것 같다.

그래서 이를 탈피하고자 이전에 Django 팀 프로젝트를 했을때 사용했던 ORM을 생각해서

공부하려고 JPA스터디를 참여하였다.

좋은 사람들을 만났고 많이 배웠으며 질문도 환영하는 느낌으로 받아들 주셨기에

길다면 길고 짧다면 짧았지만 얻어간게 정말 많았다.

지금도 질문을 하면 다들 들어주시더라

그렇게 해서 윗부분에 설명했던 테스트코드 와 같이 MybatisJPA

성공적으로 바꿨다.

쉘 스크립트 작성기

일단 지금 사용하는 스프링부트

이 프레임워크에는 내장톰캣이 구성되어있다.

근데도 과제를 수행하는 프로젝트에서 그 회사쪽의 요청으로 외장톰캣을 사용했다.

내장 톰캣이 있다고 말씀을 드렸지만 외장톰캣을 사용해야 한다고 그러시더라.

거기에 깃랩을 사용하여 소스관리를 하지만? 소스관리일뿐 그것을 가져와서 자동화는 따로 시키지 않더라.

이부분이 궁금했지만 그래도 외주를 받아서 우리가 과제를 진행했기 때문에 그쪽이 갑이어서 건의는 더이상 하지 않았던 기억이 있다.

일단 뭐 배포를 해야되는데 내가 없을 때는

프론트 개발자 한분이 전부 배포를 했다고 한다.

근데 배포하는 방식이

계속 그냥 콘솔에 cd xxx, sudo xxx, ...등등 일일이 타이핑을 치고계셔서 좀 놀랐다.

이 과정이 귀찮지는 않은가 여쭤보았고 여쭤보기도 전에 머릿속으로 당연히 번거롭고 귀찮다는걸

나도 느끼면서 질문했는데 당사자도 귀찮았다고 생각한다.

그래서 내가 할 수 있는 선에서의 최선이 바로 이 쉘스크립트 작성이었다.

이거로 인해서 반복되는 명령어 그리고 그것들을 하나하나 입력하는데에 대한 시간들에 대해

많이 줄였다. 정확하게 시간을 재지는 않았지만 빨라도 20초? 걸리던거 그냥

~: ./deploy.sh

이거만 시켜주면 되니까 이 명령어 치는데 얼마나 걸릴까???

생각은 읽는 사람의 몫이다.

뭐 이렇게 나름의 노력을 했더니 개발이 그냥 재미있다.

뭔가를 계속 찾아서 불편한 부분을 개선하는 나의 일련의 노력들 그리고 그걸 해냈을 때 희열감 그리고 그것에 대한 기억은 대단하다.

개월이 지날 수록 이런 회고글 좀 더 쓰는식으로 진행을 하겠다.

728x90

'Diary' 카테고리의 다른 글

블로그를 옮기고 최신 근황  (0) 2022.08.13
업무 리팩토링에 대한 회고  (0) 2022.08.10
1개월 1일 1커밋 회고  (0) 2022.08.07
TDD Clean Code with Java 12기 - 1주차  (0) 2022.08.05

+ Recent posts