미래 지향적인 웹 앱 구축: The Codest의 전문가 팀이 제공하는 인사이트
The Codest가 최첨단 기술로 확장 가능한 대화형 웹 애플리케이션을 제작하고 모든 플랫폼에서 원활한 사용자 경험을 제공하는 데 탁월한 성능을 발휘하는 방법을 알아보세요. Adobe의 전문성이 어떻게 디지털 혁신과 비즈니스를 촉진하는지 알아보세요...
수많은 무료 리소스, 책, 온라인 강의 코딩 부트캠프가 제공되고 있어 누구나 코딩을 배울 수 있습니다. 하지만 코딩과 소프트웨어 엔지니어링 사이에는 여전히 품질 격차가 존재합니다. 꼭 차이가 있어야 할까요?
저는 20여 년 전에 처음으로 "Hello world"를 썼는데, 누군가 코더로 일한 지 얼마나 되었냐고 묻는다면 이렇게 대답합니다. 지난 10년 동안 저는 다음과 같은 일을 하며 즐겁게 일하고 있습니다. 코드 거의 매일 전문 코더로 일한 지 얼마나 되었느냐는 질문을 받으면 이렇게 대답합니다.
얼마나 오랫동안 소프트웨어 엔지니어? 5년 정도라고 말씀드리고 싶네요. 잠깐만요, 이 수치가 믿기지 않네요! 그렇다면 무엇이 달라졌을까요? 누구를 소프트웨어 엔지니어로, 누구를 '단순한' 코더로 간주해야 할까요?
코딩은 비교적 쉽습니다. 더 이상 엄청나게 제한된 시스템에서 어셈블리 니모닉만 사용하는 것은 아닙니다. 루비처럼 표현력이 풍부하고 강력한 언어를 사용한다면 훨씬 더 쉬워집니다.
티켓을 들고 코드를 삽입할 위치를 찾고 거기에 넣어야 할 로직을 파악하면 끝입니다. 조금 더 고급이라면 코드가 예쁜지 확인합니다. 논리적으로 메서드로 분할되어 있습니다. 만족스러운 경로만 테스트하는 것이 아니라 적절한 사양을 갖추고 있습니다. 이것이 좋은 코더가 하는 일입니다.
소프트웨어 엔지니어는 더 이상 메서드와 클래스 단위로 생각하지 않습니다. 제 경험상 소프트웨어 엔지니어는 흐름으로 생각합니다. 그들은 무엇보다도 시스템을 관통하는 데이터와 상호 작용의 천둥처럼 거세게 흐르는 강물을 먼저 봅니다. 그리고 이 흐름을 돌리거나 변경하기 위해 무엇을 해야 할지 생각합니다. 예쁜 코드, 논리적 방법, 뛰어난 사양은 거의 나중에 생각하게 됩니다.
사람들은 일반적으로 현실과의 대부분의 상호작용에 대해 특정한 방식으로 생각합니다. 더 나은 용어가 없기 때문에 이를 '하향식' 관점이라고 부르겠습니다. 만약 내 뇌가 차 한 잔을 마시는 일을 하고 있다면, 먼저 부엌에 가서 주전자를 켜고, 차를 준비하고, 물을 따르고, 책상으로 돌아오는 일반적인 단계를 파악할 것입니다.
책상에 멍하니 서 있을 때 어떤 컵을 먼저 사용할지 먼저 생각하지 않고, 찬장 앞에 서 있을 때 나중에 생각하게 됩니다. 차가 다 떨어졌을 수도 있다는 것을 고려하지 않습니다. 좋은 물건). 광범위하고 반응이 느리며 오류가 발생하기 쉽습니다. 결론적으로 말하자면, 매우 인간 자연에서.
소프트웨어 엔지니어가 다소 당황스러운 데이터 흐름에 대한 변경을 고려할 때 자연스럽게 비슷한 방식으로 변경을 수행하게 됩니다. 이 사용자 스토리 예시를 살펴보겠습니다:
고객이 위젯을 주문합니다. 주문 가격을 책정할 때 다음 사항을 고려해야 합니다:
이 모든 것이 인위적으로 보일 수도 있지만(실제로도 그렇지만), 제가 최근에 접한 실제 사용자 사례와 크게 다르지 않습니다.
이제 소프트웨어 엔지니어가 이 문제를 해결하기 위해 사용할 수 있는 사고 프로세스를 살펴보겠습니다:
"먼저 사용자와 주문을 받아야 합니다. 그런 다음 총액을 계산하기 시작합니다. 0에서 시작하겠습니다. 그런 다음 위젯 모양 수정자를 적용합니다. 그런 다음 러시 차지를 적용합니다. 그런 다음 공휴일인지 확인하면 점심 전에 완료됩니다!"
아, 간단한 사용자 스토리가 가져다주는 짜릿함. 하지만 소프트웨어 엔지니어는 완벽한 멀티 스레드 기계가 아니라 인간일 뿐이며, 위의 레시피는 대략적인 방법일 뿐입니다. 엔지니어는 계속해서 더 깊이 생각해야 합니다:
"위젯 모양 수정자는... 아, 그건 위젯에 따라 많이 달라지네요. 그리고 지금은 아니더라도 향후에는 로케일마다 다를 수 있습니다." 비즈니스 요구 사항의 변화로 인해 이전에 소진되었다고 생각합니다, "러시 차지도 마찬가지일 수 있습니다. 그리고 공휴일도 지역마다 다르니 시간대도 포함될 것입니다! 여기 Rails에서 다른 시간대의 시간을 처리하는 방법에 대한 문서가 있는데... 주문 시간이 데이터베이스에 시간대와 함께 저장되는지 궁금하네요! 스키마를 확인하는 것이 좋습니다."
좋아요, 소프트웨어 엔지니어님. 멈춰요. 차 한 잔을 만들어야 하는데 찬장 앞에서 꽃이 핀 컵이 차 문제에 적용 가능한지 생각하느라 정신이 없습니다.
하지만 여러 가지 깊이로 생각하는 것과 같이 인간의 뇌에 부자연스러운 일을 하려고 할 때 쉽게 일어날 수 있는 일입니다. 동시에.
시간대 처리와 관련된 방대한 링크를 간단히 살펴본 후 엔지니어는 이를 실제 코드로 분해하기 시작했습니다. 순진한 접근 방식을 시도했다면 다음과 같이 보일 것입니다:
def calculate_price(user, order)
order.price = 0
order.price = 위젯가격.find_by(위젯_유형: 주문.위젯.유형).가격
order.price = 위젯 모양.find_by(위젯 모양: 주문.위젯.모양).modifier
...
end
이렇게 유쾌한 절차적 방식으로 계속 진행하다가 첫 번째 코드 리뷰에서 크게 멈칫하게 됩니다. 생각해 보면 큰 틀을 먼저 생각하고 세부적인 내용은 나중에 생각하는 것이 지극히 정상적이니까요. 처음에는 좋은 차가 다 떨어졌다고 생각하지 않으셨나요?
하지만 저희 엔지니어는 서비스 객체에 대해 잘 훈련되어 있고 낯선 사람이 아니므로 대신 다음과 같이 시작하겠습니다:
클래스 BaseOrderService
def self.call(user, order)
new(user, order).call
end
def initialize(user, order)
사용자 = 사용자
주문 = 주문
end
def call
"[WARN] #{self.class.name}에 대해 기본값이 아닌 호출을 구현하십시오!"
사용자, 주문
end
end
클래스 위젯프라이스서비스 < BaseOrder서비스; end
클래스 ShapePriceModifier < BaseOrderService; end
RushPriceModifier 클래스 < BaseOrderService; end
HolidayDeliveryPriceModifier 클래스 < BaseOrderService; end
클래스 OrderPriceCalculator < BaseOrderService
def call
사용자, 주문 = 위젯가격서비스.호출(사용자, 주문)
사용자, 주문 = ShapePriceModifier.call(사용자, 주문)
사용자, 주문 = 러시프라이스모디파이어.호출(사용자, 주문)
사용자, 주문 = 휴일배송가격조정자.호출(사용자, 주문)
사용자, 주문
end
end
```
좋아요! 이제 좋은 TDD를 사용하고, 테스트 케이스를 작성하고, 모든 조각이 제자리를 찾을 때까지 클래스를 구체화할 수 있습니다. 그리고 그 결과물도 아름다울 것입니다.
또한 추론하는 것도 완전히 불가능합니다.
물론 이들은 모두 하나의 책임이 있는 잘 분리된 객체입니다. 하지만 문제는 이것들이 여전히 객체라는 점입니다. "이 객체를 강제로 함수인 것처럼 보이게 하는" 서비스 객체 패턴은 정말 버팀목입니다. 누구든지 다음과 같이 호출하는 것을 막을 수 있는 것은 없습니다. 휴일 배송 가격 수정자.new(사용자, 주문).something_else_전체
. 사람들이 이러한 객체에 내부 상태를 추가하는 것을 막는 것은 없습니다.
말할 것도 없이 사용자
그리고 주문
도 객체이며, 이를 건드리는 것은 누군가 몰래 몰래 주문.저장
이 "순수한" 기능 객체 어딘가에서 기본 소스, 즉 데이터베이스의 상태를 변경합니다. 이 인위적인 예시에서는 큰 문제가 되지 않지만, 시스템이 복잡해지고 비동기적인 부분이 추가로 확장되면 문제가 될 수 있습니다.
엔지니어는 올바른 아이디어를 가지고 있었습니다. 그리고 이 아이디어를 매우 자연스럽게 표현하는 방법을 사용했습니다. 그러나 이 아이디어를 아름답고 추론하기 쉬운 방식으로 표현하는 방법을 아는 것은 기본 OOP 패러다임에 의해 거의 막혔습니다. 그리고 자신의 생각을 데이터 흐름의 전환으로 표현하는 데 아직 도약하지 못한 사람이 기본 코드를 덜 능숙하게 변경하려고 시도하면 나쁜 일이 일어날 수 있습니다.
데이터 흐름의 관점에서 아이디어를 표현하는 것이 쉬울 뿐만 아니라 필수적인 패러다임이 있다면 어떨까요? 추론이 원치 않는 부작용을 초래할 가능성 없이 단순해질 수 있다면. 차를 담는 꽃이 만발한 컵처럼 데이터가 불변할 수 있다면.
네, 물론 농담입니다. 이러한 패러다임이 존재하며 이를 함수형 프로그래밍이라고 합니다.
위의 예가 개인적으로 즐겨 사용하는 Elixir에서 어떻게 보일지 생각해 보겠습니다.
def모듈 위젯가격 do
def priceorder([user, order]) do
[사용자, 주문]
|> 위젯가격
|> 모양가격수정자
|> 러시프라이스모디파이어
|> holidaypricemodifier
end
defp widgetprice([사용자, 주문]) do
%{위젯: 위젯} = 주문
가격 = 위젯리포.getbase_가격(위젯)
[사용자, %{주문 | 가격: 가격 }]
end
defp shapepricemodifier([사용자, 주문]) do
%{위젯: 위젯, 가격: 현재가격} = 주문
modifier = 위젯리포.getshapeprice(위젯)
[사용자, %{주문 | 가격: 현재가격 * 수정자} ] ]
end
defp 러시가격수정자([사용자, 주문]) do
%{러시: 러시, 가격: 현재가격} = 주문
if rush do
[user, %{주문 | 가격: 현재가격 * 1.75} ] ]
else
[user, %{주문 | 가격: 현재_가격} ]
end
end
defp holidaypricemodifier([사용자, 주문]) do
%{날짜: 날짜, 가격: 현재가격} = 주문
modifier = HolidayRepo.getholidaymodifier(사용자, 날짜)
[사용자, %{주문 | 가격: 현재가격 * 수정자}]
end
end
```
사용자 스토리가 실제로 어떻게 구현될 수 있는지에 대한 완전한 예시라고 할 수 있습니다. 그 이유는 루비에서보다 훨씬 간단하기 때문입니다. Elixir만의 몇 가지 주요 기능을 사용하고 있습니다(일반적으로 함수형 언어에서 사용할 수 있음):
순수한 기능. 실제로 수신되는 주문
초기 상태에 대한 새로운 반복 작업인 새 복사본을 만드는 것뿐입니다. 우리는 아무것도 변경하기 위해 옆으로 뛰어다니지 않습니다. 그리고 바꾸고 싶어도 그러지 않습니다, 주문
는 단지 "멍청한" 지도일 뿐입니다. 주문.저장
가 무엇인지 모르기 때문입니다.
패턴 일치. ES6의 디스트럭처링과 유사하게, 이 기능을 사용하면 가격
그리고 위젯
주문에서 벗어나서 전달할 수 있습니다. 위젯 리포
그리고 홀리데이 리포
를 통해 전체 주문
.
파이프 운영자. 에 표시됨 가격_주문
를 실행해 본 사람이라면 누구나 익숙한 개념인 일종의 '파이프라인'으로 함수를 통해 데이터를 전달할 수 있습니다. ps aux | grep postgres
를 클릭해 실행 중인지 확인합니다.
부작용은 우리 사고 과정의 기본적인 부분이 아닙니다. 컵에 물을 부은 후 주전자에 이상이 생겨 과열되어 폭발할지도 모른다는 걱정은 일반적으로 하지 않습니다. 적어도 누군가 실수로 물을 남기지 않았는지 확인하기 위해 주전자 내부를 들여다볼 정도는 아닙니다. 폭발_후_붓기
높게 뒤집혔습니다.
코더에서 소프트웨어 엔지니어가 되는 길은 객체와 상태에 대한 고민을 넘어 데이터 흐름에 대한 고민으로 이어지는데, 경우에 따라 몇 년이 걸릴 수도 있습니다. OOP 출신인 저 역시 마찬가지였습니다. 함수형 언어를 사용하면 흐름에 대해 생각하게 됩니다. 첫날 밤에.
우리는 소프트웨어 엔지니어링 우리 자신과 이 분야에 처음 입문하는 모든 사람에게 복잡합니다. 프로그래밍이 어렵고 머리 아프게 할 필요는 없습니다. 쉽고 자연스러울 수 있습니다.
복잡하게 만들지 말고 바로 기능에 들어가자. 그것이 저희의 생각이기 때문입니다.
또한 읽어보세요: