Programımda çok büyük bir dizi kuralları ve sihir sayılarını nasıl yönetirim?


21

Programlamada biraz yeniyim (ticari olarak makine mühendisiyim) ve çalışmama sırasında tesisin çeşitli yerlerinden gelen çeşitli insanlardan gelen girdilere dayanan küçük bir program geliştiriyorum.

Yalnızca birkaç girişe (6 kesin) dayanarak, her biri bir düzine parametreye ulaşabilen yüzlerce API çağrısı yapmam gerekiyor; tümü, rolü üstlenen herkesle mülakat yaptıktan sonra topladığım bir dizi kural tarafından oluşturuldu. Kodumun kurallar ve parametreler bölümü 250 satır ve büyüyor.

Peki kodumu okunabilir ve yönetilebilir tutmanın en iyi yolu nedir? Tüm sihir numaralarım, kodun tüm kuralları, algoritmaları ve prosedür bölümlerini nasıl bölümlendiririm? Çok ayrıntılı ve ayrıntılı bir API ile nasıl başa çıkabilirim?

Asıl amacım birisine kaynağımı teslim edebilmek ve girdilerim olmadan ne yaptığımı anlamalarını sağlamak.


7
Bu API çağrıları için bazı örnekler verebilir misiniz?
Robert Harvey


"Bilgisayar bilimindeki tüm problemler başka bir dolaylı seviye ile çözülebilir" - David Wheeler
Phil Frost

... çok fazla dolaylı seviye hariç :)
Dan Lyons

1
Kodunuzu görmeden sorunuzu yanıtlamak zor. Kodunuzu codereview.stackexchange.com adresinde gönderebilir ve diğer programcılardan tavsiye alabilirsiniz.
Gilbert Le Blanc

Yanıtlar:


26

Tanımladığınız şeye dayanarak, muhtemelen veritabanlarının harika dünyasını keşfetmek isteyeceksiniz. Tanımladığınız sihirli sayıların birçoğuna benziyor - özellikle kısmen bağımlıysalar - kod değil, gerçekten veridir. Daha iyi şansa sahip olacaksınız ve uzun vadede uygulamayı genişletmeyi çok daha kolay bulacaksınız, eğer verilerin parçalarla olan ilişkisini sınıflandırabilir ve bunun için bir veritabanı yapısı tanımlayabilirsiniz.

Unutmayın, 'veritabanları' mutlaka MySQL veya MS-SQL anlamına gelmez. Verileri nasıl sakladığınız programın nasıl kullanıldığı, nasıl yazdığınız vb. Konulara bağlı olacaktır. SQL tipi bir veritabanı anlamına gelebilir veya yalnızca biçimlendirilmiş bir metin dosyası anlamına gelebilir.


7
Daha büyük problemleri varmış gibi görünmesine rağmen verileri veritabanında kodlamaya karar verdi.
Robert Harvey

Tamamen farklı parçalar oluşturan bir program yaratıyor olsaydım, evet, bu yol böyle olurdu. Yine de dört farklı konfigürasyona sahip sadece bir kısım. Asla büyük bir şey olmayacak (böyle bir şey yapmak için bir geliştirici tutmazlarsa, bu önemli değil). Yine de, bitirdikten ve yeniden canlandırmak istedikten sonra harika bir öğrenme deneyimi olacağını düşünüyorum.
user2785724

1
Yumuşak kodlamaya benziyor . Veritabanları değişken durum içindir. Sihirli sayılar tanım gereği değişken değildir.
Phil Frost

1
@PhilFrost: Bunları değişmez yapabilirsiniz. İlk tablo oluşturduktan sonra sadece onlara yazmayın.
Robert Harvey,

1
@PhilFrost: Şey, şimdi uğraştığı API'yi gördüm . Sadece büyüklüğü için dikkat çekicidir. İstemediği sürece bir veritabanına ihtiyacı olmayabilir.
Robert Harvey,

14

Bunu birden fazla bölüme genişletmeyi ummuyorsanız, henüz bir veritabanı eklemek konusunda isteksizim. Bir veritabanına sahip olmak, sizin için öğrenmeniz gereken büyük bir yığın ve diğer insanlar için çalışmasını sağlamak için kurulacak daha fazla şey anlamına gelir. Gömülü bir veritabanı eklemek, son çalıştırılabilir dosyayı taşınabilir tutar, ancak kaynak kodunuzla çalışan birisinin çalışması için bir şey daha var.

Açıkça adlandırılmış sabitlerin ve kural uygulama işlevlerinin bir listesinin çok yardımcı olacağını düşünüyorum. Her şeye doğal isimler verir ve okuryazar programlama tekniklerine odaklanırsanız, okunabilir bir program yapabilmeniz gerekir.

İdeal olarak şunu yazan kodla bitirdiniz:

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

Sabitlerin ne kadar yerel olduğuna bağlı olarak, onları mümkün olan yerlerde kullandıkları fonksiyonlarda bildirmek için can atıyorum. Açmak için oldukça yararlıdır:

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

içine

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

Bu, size büyük ölçüde kendi kendini belgeleyen kodlar verir ve ayrıca kodu ekleyenlere benzer şekilde anlamlı adlar vermesi için kodu değiştiren herkesi teşvik eder. Yerel başlatmak, ayrıca biriktireceğiniz toplam sabit sayı ile başa çıkmayı kolaylaştırır. Değerin istediğiniz olan olduğundan emin olmak için uzun bir sabit listeyi kaydırmaya devam etmeniz gerekirse, biraz can sıkıcı olur.

İsimler için bir ipucu: en önemli kelimeyi sola yazın. Oldukça iyi okunmayabilir, ama bir şeyleri bulmayı kolaylaştırır. Çoğu zaman bir karyola bakıyorsunuz ve bir cıvatayı merak ediyor, bir cıvataya bakmıyor ve nerede olduğunu merak ediyorsanız, bu yüzden buna SumpBoltThreadPitch değil BoltThreadPitchSump diyoruz. Ardından sabitlerin listesini sıralayın. Daha sonra, tüm diş ipliği aralıklarını çıkarmak için listeyi bir metin editöründe alabilir ve find işlevini kullanabilir ya da sadece "ThreadPitch" içeren satırları döndürmek için grep benzeri bir araç kullanabilirsiniz.


1
Ayrıca Fluent arayüzü oluşturmayı da düşünün
Ian

İşte kodumdan gerçek bir satır. Değişken isimlerinin ne anlama geldiğini biliyorsanız, burada neler olup bittiğinin bir anlamı var mı (argümanlar x1, y1, z1, x2, y2, z2'dir). .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

Sabitleri bulmak için ctags'ı editör entegrasyonu ile de kullanabilirsiniz .
Phil Frost

3
@ user2785724 Bu bir karışıklık. Ne yapıyor? Belirli bir uzunlukta ve derinlikte bir oluk açıyor mu? Sonra denilen bir işlev oluşturabilirsiniz createGroove(length, depth). Ne yapmak istediğinizi tanımlayan fonksiyonları, bir makine mühendisine tarif ettiğiniz gibi uygulamanız gerekir. Okuryazar programlama budur.
Phil Frost

Bu , 3B alanda bir satır çizmek için API çağrısıdır . 6 argümanın her biri programda farklı satırlardadır. Tüm API delilik. Nerede olabileceğini bilmiyordum, o yüzden orada yaptım. API çağrısının ne olduğunu ve argümanlarını bilseydiniz, bitiş noktalarının ne olduğunu, size tanıdık parametreleri kullanarak ve onu tekrar bölümle ilişkilendirebilirdiniz. SolidWorks'ü tanımak istiyorsanız, API kesinlikle labirenttir.
user2785724

4

Sorunuzun azaldığını düşünüyorum: Bir hesaplamayı nasıl yapılandırabilirim? Lütfen kod olan "kurallar dizisini" ve veri olan "sihir numaralarını" yönetmek istediğinizi unutmayın. (Bunları "kodunuza gömülü veriler" olarak görebilirsiniz, ancak yine de verileridir).

Ayrıca, kodunuzu "başkalarına anlaşılır hale getirme" aslında tüm programlama paradigmalarının genel amacıdır (örneğin, örneğin , Kent Beck'in " Uygulama Kalıpları " veya Robert C. Martin tarafından aynı amacı belirten yazılımlar için " Temiz Kod " bakınız). Herhangi bir program için, sizin gibi ).

Bu kitaplardaki tüm ipuçları sorunuza uygulanacaktır. Özel olarak "sihirli sayılar" ve "kural kümeleri" için bazı ipuçlarını çıkarmama izin verin:

  1. Sihirli sayıları değiştirmek için adlandırılmış sabitleri ve numaralandırmaları kullanın.

    Sabitlerin örneği :

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

    Adlandırılmış bir sabit ile değiştirilmelidir, böylece daha sonra hiçbir değişiklik yazım hatası yaratamaz ve örneğin ilkini değiştirerek ikinciyi değiştirerek kodunuzu kıramazsınız 0.625.

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

    Numaralandırma Örneği :

    Numaralandırmalar, birbirine ait verileri bir araya getirmenize yardımcı olabilir. Java kullanıyorsanız, Enums'ın nesne olduğunu unutmayın; Öğeleri veri tutabilir ve tüm öğeleri döndüren yöntemleri tanımlayabilir veya bazı özellikleri denetleyebilirsiniz. Burada bir Enum başka bir Enum yapımında kullanılır:

    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
    }
    

    Avantaj: şimdi kimse yanlış olmayan bir EnginePart'ı yanlış tanımlayamaz. çelik ya da karbondan yapılmış ve kimse bunun içeriğine kontrol edilecek olan bir dize olsaydı durum olurdu bir EnginePart, "asdfasdf" denilen tanıtabilirsiniz.

  2. Strateji desen ve Fabrika yöntemi desen "kurallar" saklanması ve markaları, kullanım şeyler inşa ediyor Fabrika desen durumunda (bunların kullandıkları başka bir nesneye onları geçmek için nasıl tarif; Strateji deseni durumunda, kullanım ne istersen

    Fabrika yöntemi kalıbı örneği :

    Her bölüm bir: Motorların iki tür düşünün sahiptir Kompresör bağlanacak ve her parçası serbestçe ne olursa olsun diğer bölgelerine bağlanabilir biri. Wikipedia'dan uyarlandı

    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();
        }
    }
    

    Ve sonra başka bir sınıfta:

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

    İlginç olan kısım: şimdi AssemblyLine kurucunuz hangi motor türünü kullandığı ile ayrılmıştır. Belki addEngineyöntemler uzak bir API çağırıyordur ...

    Strateji kalıbı örneği :

    Strateji modeli, davranışını değiştirmek için bir işlevin bir nesneye nasıl dahil edileceğini açıklar. Bazen bir parçayı parlatmak istediğinizi, bazen boyamak istediğinizi ve varsayılan olarak kalitesini incelemek istediğinizi düşünelim. Bu, Stack Overflow'tan uyarlanmış bir Python örneğidir.

    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"
    

    Bunu, gerçekleştirilmesini istediğiniz Eylemlerin listesini tutmaya ve sonra bunları executeyöntemden çağırmaya genişleterek genişletebilirsiniz . Belki bu genelleme daha iyi bir Oluşturucu deseni olarak tanımlanabilir , ancak hey, seçici olmak istemeyiz, değil mi? :)


2

Bir kural motoru kullanmak isteyebilirsiniz. Bir kural motoru, bu soruda açıklandığı gibi , belirli bir sonuç için gereken kriterleri anlaşılır bir şekilde modellemek için tasarlanmış bir DSL (Etki Alanına Özel Dil) verir .

Kural motorunun uygulanmasına bağlı olarak, kurallar, kodu tekrar derlemeden değiştirilebilir. Kurallar kendi basit dillerinde yazıldığından, kullanıcılar tarafından da değiştirilebilirler.

Şanslıysanız, kullanmakta olduğunuz programlama dili için kullanıma hazır bir kural motoru vardır.

Dezavantajı, bir programlama acemi iseniz zor olabilir bir kural motoru ile tanışmak zorunda olmasıdır.


1

Bu soruna olan çözümüm oldukça farklı: katmanlar, ayarlar ve LOP.

Önce API'yi bir katmana sarın. Birlikte kullanılan API çağrıları dizilerini bulun ve bunları kendi API çağrılarınızla birleştirin. Sonunda, temel API'ye doğrudan çağrı yapılmamalı, yalnızca paketleyicilere çağrı yapılmalıdır. Paketleyici çağrıları mini bir dil gibi görünmeye başlamalıdır.

İkincisi, bir 'ayar yöneticisi' uygulayın. Bu, isimleri dinamik olarak değerlerle ilişkilendirmenin bir yoludur. Bunun gibi bir şey. Başka bir mini dil.

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

Son olarak, tasarımları ifade etmek için kendi dilinizi kullanın (bu Dil Odaklı Programlama'dır). Bu dil, kurallara ve ayarlara katkıda bulunan mühendisler ve tasarımcılar tarafından anlaşılabilir olmalıdır. Akla gelen böyle bir ürünün ilk örneği Gnuplot'tur, fakat daha birçokları vardır. Kişisel olarak kullanmamama rağmen Python'u kullanabilirsiniz.

Bunun karmaşık bir yaklaşım olduğunu ve probleminiz için ümitsiz olabileceğini veya henüz edinmediğiniz becerileri gerektirdiğini biliyorum. Sadece nasıl yapardım.


0

Soruyu doğru anladığımdan emin değilim, ancak bazı yapılarda bir şeyleri gruplandırmanız gerektiği gibi. C ++ kullanıyorsanız, şöyle şeyler tanımlayabilirsiniz:

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
    }
};

Bunları program başında başlatabilirsiniz:

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

Ardından API çağrılarınız şöyle görünür (imzayı değiştiremeyeceğinizi varsayarsak):

 SomeAPICall( params1.p1, params1.p2 );

API imzasını değiştirebilirseniz, yapının tamamını geçebilirsiniz:

 SomeAPICall( params1 );

Ayrıca tüm parametreleri daha büyük bir paketleyicide gruplayabilirsiniz:

struct AllTheParameters
{
    SomeParametersClass      SPC;
    SomeOtherParametersClass SOPC;
};

0

Bundan kimsenin bahsetmemesine şaşırdım ...

Dedin:

Asıl amacım birisine kaynağımı teslim edebilmek ve girdilerim olmadan ne yaptığımı anlamalarını sağlamak.

Öyleyse şunu söyleyeyim, diğer cevapların çoğu doğru yolda. Kesinlikle veritabanlarının size yardımcı olabileceğini düşünüyorum. Ancak, size yardımcı olacak başka bir şey yorum yapmak, iyi değişken isimleri ve endişelerin uygun şekilde örgütlenmesi / ayrılmasıdır.

Diğer tüm cevaplar çok teknik bir temele dayanıyor, ancak çoğu programcının öğrendiği temelleri görmezden geliyorlar. Ticaretle uğraşan bir makine olduğun için, benim tahminime göre bu dokümantasyon tarzına alışık değilsin.

İyi ve özlü değişken isimleri yorumlamak ve seçmek, okunabilirliğe son derece yardımcı olur. Hangisini anlamak daha kolay?

var x = y + z;

Veya:

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

Bu güzel bir dil bağımsızdır. Hangi platformda, IDE'de, dilde vb. Çalıştığınızın önemi yok, uygun belgeler birisinin kodunuzu anlayabilmesinin en kolay ve en kolay yoludur.

Daha sonra bu sihirli sayıları ve tonlarca kaygıyı yönetmeye başlıyor, ancak GrandmasterB'nin yorumunun bunu oldukça iyi idare ettiğini düşünüyorum.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.