변수는 어떻게 상태를 도입합니까?


11

나는 "C ++ Coding Standards"를 읽고 있었고이 줄은 다음과 같습니다.

변수는 상태를 도입하므로 가능한 한 짧은 수명을 유지하면서 가능한 한 적은 상태를 처리해야합니다.

돌연변이를 일으켜 결국 상태를 조작하는 것은 없습니까? 가능한 적은 상태를 처리해야하는 것은 무엇입니까 ?

C ++과 같은 불완전한 언어에서는 상태 관리가 실제로 수행하고 있지 않습니까? 변수 수명을 제한하는 것 외에 가능한 적은 상태처리하는 다른 방법은 무엇 입니까?

답변:


16

변경 가능한 것이 실제로 상태를 조작하지 않습니까?

예.

그리고 "작은 상태를 처리해야한다"는 것은 무엇을 의미합니까?

더 적은 상태가 더 많은 상태보다 낫다는 것을 의미합니다. 상태가 많을수록 더 복잡해집니다.

C ++과 같은 불완전한 언어에서는 상태 관리가 실제로 수행하고 있지 않습니까?

예.

가변 수명을 제한하는 것 외에 "약간의 상태를 다루는"다른 방법은 무엇입니까?

변수 수를 최소화하십시오. 다른 코드 섹션이이를 무시할 수 있도록 일부 상태를 자체 포함 된 단위로 조작하는 코드를 격리하십시오.


9

변경 가능한 것이 실제로 상태를 조작하지 않습니까?

예. C ++에서 const변수는 변수가 아닌 유일한 변수입니다.

그리고 "작은 상태를 처리해야한다"는 것은 무엇을 의미합니까?

프로그램의 상태가 적을수록 프로그램의 이해가 쉬워집니다. 따라서 필요하지 않은 상태를 도입해서는 안되며 더 이상 필요하지 않은 상태를 유지해서는 안됩니다.

C ++과 같은 불완전한 언어에서는 상태 관리가 실제로 수행하고 있지 않습니까?

C ++과 같은 다중 패러다임 언어에서는 종종 "순수한"기능 또는 상태 중심 접근 방식 또는 일종의 하이브리드 방식 중에서 선택할 수 있습니다. 역사적으로 함수형 프로그래밍에 대한 언어 지원은 일부 언어에 비해 상당히 약했지만 개선되고 있습니다.

가변 수명을 제한하는 것 외에 "약간의 상태를 다루는"다른 방법은 무엇입니까?

수명과 범위를 제한하여 객체 간의 연결을 줄입니다. 전역 변수보다는 로컬 변수를 선호하고 공용 객체 멤버보다는 개인 변수를 선호합니다.


5

state 는 무언가가 어딘가에 저장되어 나중에 참조 할 수 있음을 의미합니다.

변수를 작성하면 일부 데이터를 저장할 공간이 생깁니다. 이 데이터는 프로그램 의 상태 입니다.

이를 사용하여 작업, 변경, 계산 등을 수행합니다.

입니다 상태 는 것들 반면, 음주 상태 없습니다.

함수형 언어에서는 대부분 함수와 객체를 전달하는 것처럼 함수를 전달하는 경우가 대부분입니다. 이러한 함수에는 상태가없고 함수를 전달해도 상태가 나타나지 않습니다 (함수 자체 내부에있을 수 있음).

C ++에서 당신이 만들 수있는 함수 객체 이다, struct또는 class한 종류의 operator()()오버로드를. 이러한 함수 객체는 로컬 상태를 가질 수 있지만 프로그램의 다른 코드와 반드시 공유되는 것은 아닙니다. 펑터 (즉, 함수 객체)는 전달하기가 매우 쉽습니다. 이것은 C ++에서 기능적 패러다임을 모방 할 수있는 수준에 가깝습니다. (아파 익)

상태가 거의 없거나 아예 없다는 것은 스레드 또는 CPU간에 공유 할 수있는 것이 없으므로 경합이 발생하지 않으며 데이터 경쟁 등으로부터 보호 할 것이 없기 때문에 병렬 실행을 위해 프로그램을 쉽게 최적화 할 수 있음을 의미합니다.


2

다른 사람들은 처음 3 가지 질문에 대한 좋은 답변을 제공했습니다.

가변 수명을 제한하는 것 외에 "가능한 한 적은 상태로 처리"하는 다른 방법은 무엇입니까?

질문 # 1에 대한 핵심 답변은 그렇습니다 . 돌연변이는 결국 상태에 영향을 미칩니다. 열쇠는 사물을 변경 하지 않는 것입니다. 한 함수의 결과가 다른 함수로 직접 전달되고 저장되지 않은 함수형 프로그래밍 스타일을 사용하여 상태를 저장하는 대신 메시지 또는 이벤트를 전달하고 저장 및 업데이트하는 대신 값을 계산하는 불변 유형.

그렇지 않으면 상태의 영향을 제한 할 수 있습니다. 가시성 또는 수명을 통해.


1

그리고 "작은 상태를 처리해야한다"는 것은 무엇을 의미합니까?

즉, 클래스는 가능한 한 작아야하며 단일 추상화를 최적으로 나타냅니다. 클래스에 10 개의 변수를 넣으면 잘못된 일이 발생했을 가능성이 높으며 클래스를 리팩토링하는 방법을 알아야합니다.


1

프로그램의 작동 방식을 이해하려면 상태 변경을 이해해야합니다. 상태가 적고 코드를 사용하는 코드가 많을수록 더 쉬워집니다.

많은 전역 변수가있는 프로그램으로 작업 한 적이 있다면 암묵적으로 이해할 것입니다.


1

상태는 단순히 저장된 데이터입니다. 모든 변수는 실제로 일종의 상태이지만 일반적으로 "상태"를 사용하여 작업간에 지속되는 데이터를 나타냅니다. 단순하고 무의미한 예로, 내부 int및 보유 increment()decrement()멤버 함수를 저장하는 클래스가있을 수 있습니다 . 여기서 내부 값은이 클래스의 인스턴스 수명 동안 지속되므로 상태입니다. 즉, 값은 객체의 상태입니다.

이상적으로 클래스가 정의하는 상태는 최소한의 중복성을 통해 가능한 한 작아야합니다. 이를 통해 수업이 단일 책임 원칙을 충족하고 캡슐화를 개선하며 복잡성을 줄입니다. 객체의 상태는 해당 객체에 대한 인터페이스에 의해 완전히 캡슐화되어야합니다. 이것은 객체의 의미가 주어지면 해당 객체에 대한 작업 결과를 예측할 수 있음을 의미합니다. 상태에 액세스 할 수있는 함수 수를 최소화하여 캡슐화를 더욱 향상시킬 수 있습니다 .

이것이 세계 상태를 피하는 주요 이유 중 하나입니다. 전역 상태는 인터페이스를 표시하지 않고 객체에 대한 종속성을 도입하여이 상태를 객체의 사용자로부터 숨길 수 있습니다. 전역 종속성이있는 객체에서 작업을 호출하면 결과가 다양하고 예측할 수 없습니다.


1

돌연변이를 일으켜 결국 상태를 조작하는 것은 없습니까?

예. 그러나 전체 시스템에서 개인 상태를 조작 할 수있는 유일한 개체 인 작은 클래스의 멤버 함수 뒤에 있으면 해당 상태의 범위가 매우 좁습니다.

가능한 적은 상태를 처리해야하는 것은 무엇입니까?

변수의 관점에서 : 가능한 적은 수의 코드 행만 액세스 할 수 있어야합니다. 변수의 범위를 최소로 좁 힙니다.

코드의 관점에서 볼 때 : 가능한 한 적은 코드로 변수에 액세스 할 수 있어야합니다. 코드 줄이 할 수있는 변수의 수를 좁히 가능성 액세스 (심지어 여부를 그만큼 중요하지 않습니다 않는 문제가 여부 모든 것을 액세스 그것을 할 수 있습니다 ).

전역 변수는 최대 범위를 갖기 때문에 너무 나쁩니다. 코드베이스의 두 줄의 코드, 코드의 POV에서 액세스하더라도 전역 변수에 항상 액세스 할 수 있습니다. 변수의 POV에서 외부 링크가있는 전역 변수는 전체 코드베이스의 모든 단일 코드 행 (또는 헤더를 포함하는 모든 단일 코드 행)에 액세스 할 수 있습니다. 실제로 2 줄의 코드로만 액세스 할 수 있지만 전역 변수가 400,000 줄의 코드로 표시되는 경우, 유효하지 않은 상태로 설정되었을 때 즉시 의심 목록에 400,000 개의 항목이 표시됩니다 (아마도 빠르게 도구가 포함 된 2 개의 항목이지만 그럼에도 불구하고 즉시 목록에는 40 만 명의 용의자가 있으며 이는 장려하는 출발점이 아닙니다.

마찬가지로 전역 변수가 전체 코드베이스에서 2 줄의 코드로만 수정되기 시작하더라도 불행히도 코드베이스가 거꾸로 진화하는 경향은 그 수가 증가 할 수 있기 때문에 그 수가 급격히 증가하는 경향이 있습니다. 마감 시간을 맞추기 위해 열광적 인 개발자는이 글로벌 변수를보고 바로 가기를 수행 할 수 있음을 알고 있습니다.

C ++과 같은 불완전한 언어에서는 상태 관리가 실제로 수행하고 있지 않습니까?

일반적으로 C ++을 매우 이국적인 방식으로 사용하지 않는 한 맞춤 불변의 데이터 구조와 순수한 기능 프로그래밍을 처리해야합니다. 상태 관리가 복잡하고 복잡 할 때 대부분의 버그의 원인이기도합니다. 종종 해당 상태의 가시성 / 노출 기능입니다.

변수 수명을 제한하는 것 외에 가능한 적은 상태를 처리하는 다른 방법은 무엇입니까?

이 모든 것은 변수의 범위를 제한하는 영역에 있지만이를 수행하는 방법은 여러 가지가 있습니다.

  • 전염병과 같은 원시 전역 변수를 피하십시오. 일부 벙어리 전역 setter / getter 함수조차도 변수의 가시성을 대폭 좁히고 적어도 불변을 유지하는 방법을 허용합니다 (예 : 전역 변수를 음수 값으로 허용해서는 안되면 setter는 해당 불변을 유지할 수 있습니다). 물론 전역 변수가 될 수있는 것 위에있는 setter / getter 디자인조차도 디자인이 좋지 않습니다. 제 요점은 여전히 ​​더 나은 방법입니다.
  • 가능하면 수업을 더 작게 만드십시오. 수백 개의 멤버 함수, 20 개의 멤버 변수 및이 클래스를 구현하는 30,000 개의 코드 행을 가진 클래스는 "전역"전용 변수를 가지게되는데, 이러한 모든 변수는 30k 라인의 코드로 구성된 멤버 함수에 액세스 할 수 있기 때문입니다. 이 경우 각 멤버 함수에서 로컬 변수를 할인하면서 "상태 복잡성"이라고 말합니다 30,000*20=600,000. 그 위에 10 개의 전역 변수가 액세스 가능하면 상태 복잡성은 다음과 같습니다 30,000*(20+10)=900,000. 건강한 "상태 복잡성"(제 개인적으로 고안된 측정 항목)은 수만이 아니라 수십만이 아닌 클래스의 경우 수천 또는 그 이하이어야합니다. 무료 기능의 경우 유지 관리에 심각한 두통이 발생하기 전에 수백 또는 그 이하라고 말하십시오.
  • 위와 같은 맥락에서, 클래스의 공용 인터페이스 만 사용하여 비회원이거나 비 친구가 될 수있는 멤버 함수 또는 친구 함수로 무언가를 구현하지 마십시오. 이러한 함수는 클래스의 개인 변수에 액세스 할 수 없으므로 해당 개인 변수의 범위를 줄임으로써 오류 가능성을 줄입니다.
  • 함수에서 실제로 필요하기 전에 변수를 선언하지 마십시오 (즉, 많은 행이 필요한 경우에도 함수 상단에 모든 변수를 선언하는 레거시 C 스타일은 피하십시오). 어쨌든이 스타일을 사용한다면 최소한 더 짧은 기능을 위해 노력하십시오.

변수를 넘어서 : 부작용

위에 나열된 이러한 많은 지침은 변경 가능한 원시 상태 (변수)에 직접 액세스하는 것입니다. 그러나 충분히 복잡한 코드베이스에서는 원시 변수의 범위를 좁히는 것만으로는 정확성에 대해 쉽게 추론하기에 충분하지 않습니다.

예를 들어, 완전한 SOLID, 추상 인터페이스 뒤에 중앙 데이터 구조가있을 수 있으며, 불변 값을 완벽하게 유지할 수 있으며,이 중앙 상태의 광범위한 노출로 인해 여전히 많은 슬픔에 빠질 수 있습니다. 전역 적으로 액세스 할 수 없지만 광범위하게 액세스 할 수있는 중앙 상태의 예는 게임 엔진의 중앙 장면 그래프 또는 Photoshop의 중앙 레이어 데이터 구조입니다.

이러한 경우 "상태"라는 개념은 원시 변수를 넘어서서 데이터 구조와 그와 같은 것들에 지나지 않습니다. 마찬가지로 범위를 줄이는 데 도움이됩니다 (간접적으로 변경하는 함수를 호출 할 수있는 줄 수 줄이기).

여기에 이미지 설명을 입력하십시오

넓게 축소 된 아키텍처 수준에서 인터페이스에 액세스하는 것은 여전히 ​​간접적이지만 상태를 변경하고 있기 때문에 인터페이스를 빨간색으로 표시 한 방법에 주목하십시오. 클래스는 인터페이스의 결과로 변하지 않는 것을 유지할 수 있지만 정확성에 대한 추론 능력 측면에서만 가능합니다.

이 경우 중앙 데이터 구조는 전체적으로 액세스 할 수없는 추상 인터페이스 뒤에 있습니다. 복잡한 코드베이스의 함수로드에서 단순히 삽입 된 다음 (멤버 함수를 통해) 간접적으로 변경 될 수 있습니다.

이 경우 데이터 구조가 자체 불변량을 완벽하게 유지하더라도 이상한 일이 더 넓은 수준에서 발생할 수 있습니다 (예 : 오디오 플레이어는 볼륨 수준이 0 % ~ 100 %, 그러나 사용자가 재생 버튼을 치고 이벤트가 트리거 될 때 가장 최근에로드 한 것 이외의 임의의 오디오 클립을 가지고있는 것을 막을 수는 없습니다. 광범위한 사용자 관점에서 여전히 바람직하지 않은 결함 동작).

이러한 복잡한 시나리오에서 자신을 보호하는 방법은 코드베이스에서 기능을 호출 할 수있는 위치를 "병목"시켜서 원시 상태를 넘어 인터페이스를 넘어서는 이러한 종류의 시스템에 대한 궁극적 인 관점에서도 외부 부작용을 유발할 수 있습니다.

여기에 이미지 설명을 입력하십시오

이상하게도, "상태"(빨간색으로 표시되어 있으며 "원시 변수"를 의미하지 않으며 단지 "개체"를 의미하고 아마도 추상 인터페이스 뒤에 있음)가 수많은 곳에서 액세스되고 있음을 알 수 있습니다. . 기능은 각각 중앙 업데이터가 액세스 할 수있는 로컬 상태에 액세스 할 수 있으며 중앙 상태는 중앙 업데이터 만 액세스 할 수 있습니다 (더 이상 중앙이 아니라 로컬에 있음).

이것은 1 천만 줄의 코드에 걸친 게임과 같이 실제로 복잡한 코드베이스에만 해당되지만 소프트웨어의 정확성에 대한 추론과 숫자를 크게 제한 / 병목시킬 때 변경 사항이 예측 가능한 결과를 얻는 데 큰 도움이 될 수 있습니다 전체 아키텍처가 올바르게 작동하기 위해 회전하는 중요 상태를 변경시킬 수있는 장소

원시 변수 외에는 외부 부작용이 있으며 외부 부작용은 소수의 멤버 함수로 제한되어 있어도 오류의 원인입니다. 함수의 보트로드가 소수의 멤버 함수를 직접 호출 할 수 있다면 시스템에 함수의 보트로드가 간접적으로 외부 부작용을 일으킬 수 있으며 복잡성을 증가시킵니다. 코드베이스에 해당 멤버 함수에 액세스 할 수있는 위치가 한 곳만 있고 모든 곳에서 산발적으로 발생하는 이벤트에 의해 실행 경로가 트리거되지 않고 매우 제어되고 예측 가능한 방식으로 실행되는 경우 복잡성이 줄어 듭니다.

국가 복잡성

국가의 복잡성조차도 고려해야 할 중요한 요소입니다. 추상 인터페이스 뒤에 광범위하게 액세스 할 수있는 간단한 구조는 엉망이되지 않습니다.

복잡한 아키텍처의 핵심 논리적 표현을 나타내는 복잡한 그래프 데이터 구조는 엉망이되기 쉽고 그래프의 불변량을 위반하지 않는 방식으로 진행됩니다. 그래프는 단순한 구조보다 몇 배 더 복잡하므로 이러한 경우 코드베이스의 복잡성을 줄이고 그러한 그래프 구조에 액세스 할 수있는 장소의 수를 절대 최소값으로 줄이는 것이 더 중요합니다. 산발적으로 패러다임을 피하기 위해 풀 패러다임으로 전환하는 이런 종류의 "중앙 업데이터"전략은 어디에서나 그래프 데이터 구조에 대한 직접적인 푸시가 실제로 성과를 낼 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.