프로그램에서 매우 큰 규칙과 마법 번호를 어떻게 관리합니까?


21

나는 프로그래밍에 다소 익숙하지 않고 (나는 무역 분야의 기계 엔지니어입니다.) 가동 중지 시간 동안 공장 주변의 다양한 사람들의 의견을 기반으로 (솔리드 웍스) 부품을 생성하는 작은 프로그램을 개발 중입니다.

단지 몇 개의 입력 (정확한 6)에 기초하여, 각각 최대 12 개의 매개 변수를 취할 수있는 수백 개의 API 호출을해야합니다. 모든 부분을 처리하는 모든 사람을 인터뷰 한 후 수집 한 일련의 규칙으로 생성됩니다. 내 코드의 규칙 및 매개 변수 섹션은 250 줄로 증가하고 있습니다.

그렇다면 코드를 읽고 관리 할 수있는 가장 좋은 방법은 무엇입니까? 코드의 모든 마법 번호, 모든 규칙, 알고리즘 및 절차 부분을 어떻게 분류합니까? 매우 상세하고 세분화 된 API를 어떻게 처리합니까?

나의 주요 목표는 다른 사람에게 내 정보원을 건네주고 입력없이 내가하고있는 일을 이해하도록하는 것입니다.


7
이러한 API 호출의 예를 제공 할 수 있습니까?
Robert Harvey


"컴퓨터 과학의 모든 문제는 다른 수준의 간접 지시로 해결 될 수있다" -David Wheeler
Phil Frost

... :) 간접 너무 많은 수준 제외시켰다
댄 리용을

1
코드를 보지 않고 질문에 대답하기는 어렵습니다. codereview.stackexchange.com에 코드를 게시 하고 다른 프로그래머에게 조언을 구할 수 있습니다 .
길버트 르 블랑

답변:


26

당신이 묘사 한 것에 기초하여, 당신은 아마 훌륭한 데이터베이스 세계를 탐험하고 싶을 것입니다. 그것은 당신이 묘사하는 많은 마법의 숫자, 특히 부분적으로 의존적 인 경우 실제로 코드가 아닌 데이터라고 생각합니다. 데이터가 부품과 어떻게 관련되어 있는지 분류하고 데이터베이스 구조를 정의 할 수 있다면 훨씬 더 운이 좋으며 장기적으로 응용 프로그램을 확장하는 것이 훨씬 쉽다는 것을 알게 될 것입니다.

'데이터베이스'가 반드시 MySQL 또는 MS-SQL을 의미하는 것은 아닙니다. 데이터를 저장하는 방법은 프로그램 사용 방법, 작성 방법 등에 따라 크게 달라집니다. SQL 유형 데이터베이스를 의미하거나 단순히 형식화 된 텍스트 파일을 의미 할 수도 있습니다.


7
더 큰 문제가있는 것처럼 보이지만 데이터베이스의 데이터를 체계화하는 데 동의했습니다.
Robert Harvey

내가 완전히 다른 부분을 만드는 프로그램을 만들고 있다면, 이것이 갈 길입니다. 그러나 구성이 약간 다른 4 개의 구성 요소 만 있습니다. 개발자가 그와 같은 것을 만들기 위해 고용하지 않는 한 큰 문제가되지 않습니다. 그래도 완료하고 리팩토링하고 싶을 때 훌륭한 학습 경험이 될 것 같습니다.
user2785724

1
소프트 코딩 처럼 들립니다 . 데이터베이스는 변경 가능한 상태입니다. 매직 넘버는 정의상 변경할 수 없습니다.
Phil Frost

1
@ PhilFrost : 당신은 그것들을 불변으로 만들 수 있습니다. 초기 테이블 생성 후에는 쓰지 마십시오.
Robert Harvey

1
@ PhilFrost : 글쎄, 이제 그가 다루고 있는 API 를 보았습니다 . 크기가 엄청나게 놀랍습니다. 필요하지 않은 경우 데이터베이스가 전혀 필요하지 않을 수 있습니다.
Robert Harvey

14

이것을 여러 부분으로 확장 할 것으로 예상되지 않는 한 아직 데이터베이스를 추가하는 것을 꺼려합니다. 데이터베이스가 있다는 것은 많은 것을 배우고 다른 사람들이 사용할 수 있도록 더 많은 것을 설치하는 것을 의미합니다. 임베디드 데이터베이스를 추가하면 최종 실행 파일을 이식 가능하게 유지하지만 소스 코드를 가진 사람은 이제 한 가지 더 작업해야합니다.

명확하게 명명 된 상수 목록과 규칙 구현 함수가 많은 도움이 될 것이라고 생각합니다. 모든 것을 자연스럽게 말하고 문해력있는 프로그래밍 기술 에 집중 한다면, 읽을 수있는 프로그램을 만들 수 있어야합니다.

이상적으로는 다음과 같은 코드로 끝납니다.

LeftBearingHoleDepth = BearingWidth + HoleDepthTolerance;
if (not CheckPartWidth(LeftBearingHoleDepth, {other parameters})
    {whatever you need to adjust}

지역의 상수에 따라 가능한 경우 사용되는 함수에서 상수를 선언하고 싶습니다. 돌리는 것이 매우 유용합니다.

SomeAPICall(10,324.5, 1, 0.02, 6857);

으로

const NumberOfOilDrainHoles = 10
const OilDrainHoleSpacing = 324.5
{etc}
SomeAPICall(NumberOfOilDrainHoles, OilDrainHoleSpacing, {etc}

이를 통해 대부분 자체 문서화 코드를 제공하고 코드를 수정하는 모든 사람이 추가 한 것과 비슷한 의미의 이름을 지정할 수 있습니다. 로컬을 시작하면 누적되는 총 상수 수를보다 쉽게 ​​처리 할 수 ​​있습니다. 값이 원하는 값인지 확인하기 위해 긴 상수 목록을 계속 스크롤 해야하는 경우 약간 성가 시게됩니다.

이름에 대한 한 가지 팁 : 가장 중요한 단어를 왼쪽에 넣으십시오. 잘 읽지는 않지만 물건을 쉽게 찾을 수 있습니다. 대부분의 경우 섬프를보고 볼트에 대해 궁금해하고 볼트를 보지 않고 어디에 있는지 궁금해하므로 BoltThreadPitchSump가 아닌 SumpBoltThreadPitch라고합니다. 그런 다음 상수 목록을 정렬하십시오. 나중에 모든 스레드 피치를 추출하려면 텍스트 편집기에서 목록을 가져 와서 찾기 기능을 사용하거나 grep과 같은 도구를 사용하여 "ThreadPitch"가 포함 된 행만 반환 할 수 있습니다.


1
또한 Fluent 인터페이스 만들기를 고려하십시오
Ian

내 코드의 실제 줄은 다음과 같습니다. 변수 이름의 의미를 알고 있다면 여기에서 무슨 일이 일어나고 있는지 (인수가 x1, y1, z1, x2, y2, z2) 의미가 있습니까? .CreateLine(m_trunion_support_spacing / 2, -((m_flask_length / 2) + m_sand_ledge_width + m_wall_thickness), -m_flange_thickness, m_trunion_support_spacing / 2, -((m_flask_length / 2) + m_sand_ledge_width + m_wall_thickness), -m_flask_height + m_flange_thickness)
user2785724

편집기 통합 과 함께 ctags 를 사용 하여 상수를 찾을 수도 있습니다 .
Phil Frost

3
@ user2785724 엉망입니다. 뭐하는거야? 특정 길이와 깊이의 홈을 만들고 있습니까? 그런 다음이라는 함수를 만들 수 있습니다 createGroove(length, depth). 기계 엔지니어에게 설명 할 때 수행하고자하는 기능을 설명하는 기능을 구현해야합니다. 이것이 문맹 프로그래밍에 관한 것입니다.
필 프로스트

즉,의 API 호출을 그리는 하나 개의 3 차원 공간에서 줄을. 6 개의 인수 각각은 프로그램에서 다른 행에 있습니다. 전체 API는 미쳤다. 난장판을 어디에서 만들지 몰랐으므로 거기서 만들었습니다. API 호출과 그 인수를 알고 있다면 친숙한 매개 변수를 사용하여 엔드 포인트가 무엇인지 확인하고 다시 해당 파트와 관련시킬 수 있습니다. SolidWorks에 익숙해지기를 원한다면 API는 미로입니다.
2785724

4

나는 당신의 질문이 다음과 같이 감소한다고 생각합니다 : 어떻게 계산을 구성합니까? 코드 인 "규칙 세트"와 데이터 인 "매직 숫자 세트"를 관리하려고합니다. (이들은 "코드에 포함 된 데이터"로 볼 수 있지만 그럼에도 불구하고 데이터입니다).

또한, "다른 사람들이 이해할 수없는"코드를 만드는 것은 모든 프로그래밍 패러다임의 일반적인 목표입니다 (예 : Kent Beck의 " 구현 패턴 "또는 Robert C. Martin의 " 깨끗한 코드 "참조) 당신을 위해 같은 모든 ) 프로그램.

이 책의 모든 힌트는 질문에 적용됩니다. "마법의 숫자"와 "규칙 세트"에 대한 힌트를 추출하겠습니다.

  1. 명명 된 상수 및 열거를 사용하여 마법의 숫자를 바꿉니다.

    상수의 예 :

    if (partWidth > 0.625) {
        // doSomeApiCall ...
    }
    return (partWidth - 0.625)
    

    예를 들어 첫 번째는 변경 0.625하지만 두 번째는 변경 하지 않고 오타가 발생하여 코드가 손상되지 않도록 명명 된 상수로 바꿔야 합니다.

    const double MAX_PART_WIDTH = 0.625;
    
    if (partWidth > MAX_PART_WIDTH) {
        // doSomeApiCall ...
    }
    return (partWidth - MAX_PART_WIDTH)
    

    열거의 예 :

    열거는 함께 속한 데이터를 정리하는 데 도움이됩니다. Java를 사용하는 경우 Enum은 객체라는 것을 기억하십시오. 해당 요소는 데이터를 보유 할 수 있으며 모든 요소를 ​​반환하거나 일부 속성을 확인하는 메서드를 정의 할 수 있습니다. Enum은 다른 Enum을 구성하는 데 사용됩니다.

    public enum EnginePart {
        CYLINDER (100, Materials.STEEL),
        FLYWHEEL (120, Materials.STEEL),
        CRANKSHAFT (200, Materials.CARBON);
    
        private final double maxTemperature;
        private final Materials composition;
        private EnginePart(double maxTemperature, Materials composition) {
            this.maxTemperature = maxTemperature;
            this.composition = composition;
        }
    }
    
    public enum Materials {
        STEEL,
        CARBON
    }
    

    장점 : 이제 아무도 엔진 부품을 잘못 정의 할 수 없습니다. 강철이나 탄소로 만들어 지지 않은 없으며 "asdfasdf"라는 EnginePart를 도입 할 수 없습니다. 내용에서 확인할 문자열 인 경우와 같습니다.

  2. 전략 패턴팩토리 메소드 패턴은 "규칙"캡슐화 차종은 사용이 뭔가를 건설하고 공장 패턴의 경우 (그들을 사용하는 것이 다른 객체에게 전달하는 방법을 설명, 전략 패턴의 경우, 사용법은 원하는 것입니다).

    팩토리 메소드 패턴의 예 :

    각 부분이 하나는 엔진의 두 가지 유형이 상상 컴프레서에 연결하고, 각 부분이 자유롭게 어떤 다른 부분에 연결할 수 있습니다 하나를. 위키 백과에서 적응

    public class EngineAssemblyLine {
        public EngineAssemblyLine() {
            EnginePart enginePart1 = makeEnginePart();
            EnginePart enginePart2 = makeEnginePart();
            enginePart1.connect(enginePart2);
            this.addEngine(engine1);
            this.addEngine(engine2);
        }
    
        protected Room makeEngine() {
            return new NormalEngine();
        }
    }
    

    그리고 다른 수업에서 :

    public class CompressedEngineAssemblyLine extends EngineAssemblyLine {
        @Override
        protected Room makeRoom() {
            return new CompressedEngine();
        }
    }
    

    흥미로운 부분은 다음과 같습니다. 이제 AssemblyLine 생성자가 처리중인 엔진 유형으로 분리됩니다. 아마도addEngine 메소드가 원격 API를 호출하고 있습니다 ...

    전략 패턴의 예 :

    전략 패턴은 동작을 변경하기 위해 객체에 함수를 도입하는 방법을 설명합니다. 때로는 부품을 연마하고, 때로는 페인트를 칠하고, 기본적으로 품질을 검토하고 싶다고 상상해 봅시다. 이 예제 는 Stack Overflow에서 수정 된 Python 예제입니다

    class PartWithStrategy:
    
        def __init__(self, func=None) :
            if func:
                self.execute = func
    
        def execute(self):
            # ... call API of quality review ...
            print "Part will be reviewed"
    
    
    def polish():
        # ... call API of polishing department ...
        print "Part will be polished"
    
    
    def paint():
        # ... call API of painting department ...
        print "Part will be painted"
    
    if __name__ == "__main__" :
        strat0 = PartWithStrategy()
        strat1 = PartWithStrategy(polish)
        strat2 = PartWithStrategy(paint)
    
        strat0.execute()  # output is "Part will be reviewed"
        strat1.execute()  # output is "Part will be polished"
        strat2.execute()  # output is "Part will be painted"
    

    수행하려는 조치 목록을 보유한 후 execute메소드 에서 차례로 호출하여이를 확장 할 수 있습니다 . 이 일반화는 Builder 패턴 으로 더 잘 설명 될 수 있지만, 까다 롭고 싶지는 않습니까? :)


2

규칙 엔진을 사용할 수 있습니다. 규칙 엔진은 이 질문에 설명 된대로 이해하기 쉬운 방식으로 특정 결과에 필요한 기준을 모델링하도록 설계된 DSL (Domain Specific Language)을 제공합니다. .

규칙 엔진의 구현에 따라 코드를 다시 컴파일하지 않고도 규칙을 변경할 수도 있습니다. 규칙은 고유 한 간단한 언어로 작성되므로 사용자도 변경할 수 있습니다.

운이 좋으면 사용중인 프로그래밍 언어에 즉시 사용 가능한 규칙 엔진이 있습니다.

단점은 프로그래밍 초보자라면 어려울 수있는 규칙 엔진에 익숙해 져야한다는 것입니다.


1

이 문제에 대한 나의 해결책은 레이어, 설정 및 LOP와는 상당히 다릅니다.

먼저 API를 레이어로 래핑하십시오. 함께 사용되는 일련의 API 호출을 찾아이를 고유 한 API 호출로 결합하십시오. 결국 기본 API에 대한 직접 호출이없고 랩퍼에 대한 호출 만 있어야합니다. 랩퍼 호출은 미니 언어처럼 보이기 시작해야합니다.

둘째, '설정 관리자'를 구현하십시오. 이것은 이름과 값을 동적으로 연결하는 방법입니다. 이 같은. 또 다른 미니 언어.

Baseplate.name="Base plate"
Baseplate.length=1032.5
Baseplate.width=587.3

마지막으로, 디자인을 표현할 수있는 자신 만의 미니 언어를 구현하십시오 (언어 지향 프로그래밍). 이 언어는 규칙과 설정에 기여하는 엔지니어와 디자이너가 이해할 수 있어야합니다. 그런 제품의 첫 번째 예는 Gnuplot이지만 다른 제품도 많이 있습니다. 개인적으로는 그렇지 않지만 Python을 사용할 수 있습니다.

이 방법은 복잡한 접근 방식이므로 문제가 발생하거나 아직 습득하지 못한 기술이 필요할 수 있습니다. 내가 어떻게 할까.


0

질문을 올바르게 받았는지 확실하지 않지만 일부 구조로 그룹화 해야하는 것처럼 들립니다. C ++를 사용하고 있다면 다음과 같이 정의 할 수 있습니다.

struct SomeParametersClass
{
    int   p1;  // this is for that
    float p2;  // this is a different parameter
    ...
    SomeParametersClass() // constructor, assigns default values
    {
        p1 = 42; // the best value that some guy told me
        p2 = 3.14; // looks like a know value, but isn't
    {
};

struct SomeOtherParametersClass
{
    int   v1;  // this is for ...
    float v2;  // this is for ...
    ...
    SomeOtherParametersClass() // constructor, assigns default values
    {
        v1 = 24; // the best value 
        v2 = 1.23; // also the best value
    }
};

프로그램을 시작할 때 다음을 설명 할 수 있습니다.

int main()
{
    SomeParametersClass params1;
    SomeOtherParametersClass params2;
    ...

그러면 API 호출은 다음과 같습니다 (서명을 변경할 수 없다고 가정).

 SomeAPICall( params1.p1, params1.p2 );

API의 서명을 변경할 수 있으면 전체 구조를 전달할 수 있습니다.

 SomeAPICall( params1 );

모든 매개 변수를 더 큰 래퍼로 그룹화 할 수도 있습니다.

struct AllTheParameters
{
    SomeParametersClass      SPC;
    SomeOtherParametersClass SOPC;
};

0

다른 사람이 이것을 언급하지 않은 것에 놀랐습니다 ...

당신은 말했다 :

나의 주요 목표는 다른 사람에게 내 정보원을 건네주고 입력없이 내가하고있는 일을 이해하도록하는 것입니다.

다른 대답들 대부분이 올바른 길을 가고 있습니다. 나는 데이터베이스가 당신을 도울 수 있다고 생각합니다. 그러나 당신을 도울 또 다른 것은 논평, 좋은 변수 이름, 적절한 조직 / 우려의 분리입니다.

다른 모든 답변은 기술을 많이 사용하지만 대부분의 프로그래머가 배우는 기본 사항은 무시합니다. 당신은 무역에 의한 기계 공학이기 때문에, 당신은 이런 스타일의 문서에 익숙하지 않다고 생각합니다.

간결하고 좋은 간결한 변수 이름을 언급하고 선택하면 가독성이 크게 향상됩니다. 어느 것이 더 이해하기 쉬운가요?

var x = y + z;

또는:

//Where bandwidth, which was previously defined is (1000 * Info Rate) / FEC Rate / Modulation * carrier spacing / 1000000
float endFrequency = centerFrequency + (1/2 bandwidth);

이것은 언어에 독립적입니다. 어떤 플랫폼, IDE, 언어 등을 사용하든, 적절한 문서화는 누군가가 코드를 이해할 수 있도록 가장 깨끗하고 쉬운 방법입니다.

다음으로 그 마법의 수와 수많은 우려를 관리해야하지만 GrandmasterB의 의견이 그것을 잘 처리했다고 생각합니다.

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