'Refactoring a Function in Elixir'블로그 포스터 읽어주세요.

18294 단어 Elixirtech
이 글은 Elixir Advent Calendar 2020 16일째다.전날, @ringo156선생님의 Elixir에 Twitter를 만드는 bot입니다.입니다!
오늘 제가 Elixir의 블로그를 검색했을 때 Dock Yard에서 보내온'Refactoring a Function in Elixir라는 블로그를 발견했습니다. 내용에 대해 접촉하고 싶습니다.
https://dockyard.com/blog/2020/04/01/refactoring-a-function-in-elixir

뭐라고 써있어요?


Designing Elixir Systems with OPP'라는 책의 Part.1에 적힌 디자인 원칙에 따라 간단한 게임 퀴즈의 팩스 예시를 총결하였다.
https://pragprog.com/titles/jgotp/designing-elixir-systems-with-otp/
(이 책은 자주 볼 수 있어요. 영문판도 있고 읽을 가치도 높다고 생각해서 이 기사를 쓰면서 넘겼어요.😇)
아래의 기본 원칙에 따라 팩스를 진행한다.
  • Build functions at a single level of abstraction
  • SLA 또는 SLAP
  • 이라 함
  • Make decisions in function heads where possible
  • '함수 머리로 조건분지를 표현하면'을 읽으면 되나요?
  • 다시 말하면 함수 모드의 일치로 표현할 수도 있다
  • 함수 머리라는 용어여기 기사.는 이해하기 쉽다
  • Name concepts with functions
  • "개념, 개념은 함수로 명명하라"는 느낌 아닐까
  • 이름이 중요한 부분이죠
  • Shape functions for composition
  • 는'합성된 내용에 따라 함수를 쓴다','소함수를 쓰고 pipe로 조립하여 가능한 상태에서 쓴다'
  • 로 해석한다.
  • Build single-purpose functions
  • "한 가지 일만 하는 함수를 써라"
  • 큰 함수가 되기 쉬우나 중요하다
  • 제목.

  • 마스코트 게임?(외국에서 유명한가? 나만 몰랐나 봐) 논리가 주제
  • 정답을 맞힌 후 사용자의 득점 처리를 하고 다음 문제로 들어갑니다.틀리면 error
  • 이런 느낌인가?다음은 원 코드입니다.
    # game.ex
    
    def answer_question(%Game{current_question: current_question} = game, guess, user_name) do
        case(current_question.mascot == guess) do
          true ->
            game =
              game
              |> increase_score_for_user(user_name)
              |> check_for_winning_user(user_name)
              |> Map.put(:current_question, get_random_question())
    
            {:ok, game}
    
          _ ->
            {:error, :incorrect}
        end
      end
    

    step1: Functions should be at single layer of abstraction


    먼저 SLAP을 기준으로 수정합니다.
  • case(current_question.mascot == guess) do - We’re making a decision at the guess level to determine if the response from a user is correct
  • game = game |> increase_score_for_user(user_name) |> check_for_winning_user(user_name) - Game level operations occur as we a advance our game token.
  • |> Map.put(:current_question, get_random_question()) - Elixir datatypes level abstraction
  • case문장의 조건분지
  • 게임에 대한 처리(≈상업논리)
  • Elixir의 데이터 구조(map)에 대한 처리
  • 입도가 분산되어 있기 때문에 우선 이곳을 깨끗하게 해야 한다.
    먼저, 맵.put에서 쓴 부분을 pipe와 연결된 다른 함수의 입도와 일치하도록 이름을 붙여 함수에 잘라냅니다.
    수정되면 이쪽이야.Map.putElixir의 데이터 구조는 간단한 처리로 쓴 함수select_question/1.이에 따라 level 완비increase_score_for_userして、check_for_winning_userして、select_questionして...의 경우.
    # game.ex
    
    def answer_question(%Game{current_question: current_question} = game, guess, user_name) do
        case(current_question.mascot == guess) do
          true ->
            game =
              game
              |> increase_score_for_user(user_name)
              |> check_for_winning_user(user_name)
              |> select_question() # add select_question() to the pipeline
    
            {:ok, game}
    
          _ ->
            {:error, :incorrect}
        end
    end
    
    defp select_question(game) do
      Map.put(game, :current_question, get_random_question())
    end
    
    다음으로 수정case(current_question.mascot == guess) do한 부분.이것도 "사용자 정답인가요?"이런 논리는 함수화되지 않았기 때문에 수정할 여지가 있다.
    '사용자의 대답'Response을 정리하고 구조체를 준비해 거기correct에서 키를 만든다.correct에 correct?/2의 결과를 입력하십시오.
    # response.ex
    
    defmodule Response do
      defstruct ~w[guess game user_name correct]a
    
      def new(game, guess, user_name) do
        %__MODULE__{
          guess: guess,
          user_name: user_name,
          game: game,
          correct: correct?(game, guess)
        }
      end
    
      defp correct?(%Game{current_question: current_question} = game, guess) do
        current_question.mascot == guess
      end
    end
    
    이 구조체를 사용하여 게임의 논리를 개작한다.
    # game.ex
    
    def answer_question(%Game{current_question: current_question} = game, guess, user_name) do
        response = Response.new(game, guess, user_name)
        case(response.correct) do
          true ->
            game =
              game
              |> increase_score_for_user(user_name)
              |> check_for_winning_user(user_name)
              |> select_question() # add select_question() to the pipeline
    
            {:ok, game}
    
          _ ->
            {:error, :incorrect}
        end
    end
    
    단번에 이 코드가'최초에 정확했는지 부정확했는지 알 수 있다.정답이 정확하지 않은 조건을 나누면increasescore_for_user→check_for_winning_user→select_question과 논리를 통해 일목요연하게 변했다.

    step2: Make decisions in function heads where possible


    가능하면 조건 차이를 함수 헤더로 기술하는 방침으로 코드를 바꿉니다.
    결론이 명확하게 쓰여 있는 코드.세 개의 동명 함수를 설명하고 패턴이 두 번째 파라미터와 일치합니다
  • guess(string)
  • %Response{correct: true}
  • %Response{correct: false}
  • 분기 1
    # game.ex
    
    def answer_question(%Game{current_question: current_question} = game, guess, user_name)
        when is_binary(guess) do
      response = Response.new(game, guess, user_name)
      answer_question(game, response, user_name)
    end
    
    def answer_question(game, %Response{correct: true} = response, user_name) do
      game =
        game
        |> increase_score_for_user(user_name)
        |> check_for_winning_user(user_name)
        |> select_question()
    
      {:ok, game}
    end
    
    def answer_question(_game, %Response{correct: false} = response, _user_name) do
      {:error, :incorrect}
    end
    
    코드가 유창하다.다만, 개인도 원래 케이스 코드를 즐겨 사용한다.업무 시스템 등에서 의견 차이 조건 자체에 강한 관심이 있다면 원래의 케이스가 코드를 읽을 때 한꺼번에 따라잡기 쉽다고 생각합니다.

    총결산


    이것은 원칙을 폭로할 수 있는 좋은 블로그이다🎉
    입도에 주의하고 적절한 명칭의 함수를 써야 한다.실수로 Enum과 Map을 단번에 다 써버렸어요. "어? 이 목록을 어떻게 처리하고 싶어요?"이것도 쉽게 이런 상태에 빠지게 한다(Elixir든 다른 언어든 그렇지만)
    원리 원칙을 준수하고 테스트, 유지보수가 쉬우므로 가독성이 높은 코드를 쓸 수 있기를 바랍니다🚀

    좋은 웹페이지 즐겨찾기