11000 satır C ++ kaynak dosyası hakkında ne yapmalı?


229

Bu yüzden bizim projemizde bu büyük (11000 satır büyük mü?) Mainmodule.cpp kaynak dosyasına sahibiz ve her dokunuşta elimden geçiyorum.

Bu dosya çok merkezi ve büyük olduğundan, daha fazla kod biriktirmeye devam ediyor ve aslında küçülmeye başlamak için iyi bir yol düşünemiyorum.

Dosya, ürünümüzün birkaç (> 10) bakım sürümünde kullanılır ve aktif olarak değiştirilir ve bu nedenle yeniden düzenlenmesi gerçekten zordur. "Basitçe" bölmek, bir başlangıç ​​için, 3 dosyaya olsaydım, bakım sürümlerindeki değişiklikleri geri almak kabus haline gelecektir. Ayrıca, böyle uzun ve zengin bir geçmişe sahip bir dosyayı böldüğünüzde,SCC aniden çok daha zorlaşır.

Dosya temel olarak programımızın "ana sınıfını" (ana dahili iş dağıtımı ve koordinasyonu) içerir, bu nedenle her özellik eklendiğinde, bu dosyayı ve her büyüdüğünde de etkiler. :-(

Bu durumda ne yapardınız? Yeni özelliklerin bozulmadan ayrı bir kaynak dosyaya nasıl taşınacağı hakkında herhangi bir fikirSCCİş akışını mı?

(Araçlarla ilgili not: C ++ ile birlikte kullanıyoruz Visual Studio; AccuRevolarak kullanıyoruz SCCama bence SCCburada gerçekten önemli değil; Araxis MergeDosyaların gerçek karşılaştırmasını ve birleştirmesini yapmak için kullanıyoruz )


15
@BoltClock: Aslında Vim oldukça hızlı açacak.
ereOn

58
69305 satır ve sayma. Bizim uygulamada meslektaşım kodunun çoğunu dökümü bir dosya. Buraya gönderme direnemedim. Şirketimde bunu bildirecek kimse yok.
Agnel Kurian

204
Anlamıyorum. "Bu işten çıkın" yorumu nasıl bu kadar çok oy alabilir? Bazı insanlar, tüm projelerin sıfırdan yazıldığı ve / veya% 100 çevik, TDD, ... kullandıkları bir peri topraklarında yaşıyor gibi görünüyor (buzzwords'lerinizi buraya koyun).
Stefan

39
@ Stefante: Benzer bir kod tabanı ile karşılaştığımda tam olarak bunu yaptım. Zamanımın% 95'ini 10 yaşındaki bir kod tabanında kabaca çalışmayı ve% 5'i aslında kod yazmayı harcamaktan hoşlanmadım . Sistemin bazı yönlerini test etmek aslında imkansızdı (ve birim testi demek istemiyorum, aslında çalışıp çalışmadığını görmek için kodu çalıştırın ). 6 aylık deneme süremi bitirmedim, kaybetme savaşlarından ve dayanamadığım kod yazmaktan bıktım.
İkili Worrier

50
dosyayı bölmenin geçmiş izleme yönüyle ilgili olarak: tüm dosyayı bölmek istediğiniz kadar kopyalamak için sürüm kontrol sisteminizin copy komutunu kullanın ve ardından istemediğiniz her kopyadaki tüm kodu kaldırın bu dosyada. Bu, genel geçmişi korur, çünkü bölünmüş dosyaların her biri geçmişini bölünme boyunca izleyebilir (bu, dosyanın içeriğinin çoğunun dev bir silinmesine benzeyecektir).
rmeador

Yanıtlar:


86
  1. Dosyada nispeten kararlı (hızlı değişmeyen ve dallar arasında çok fazla değişmeyen) ve bağımsız bir birim olarak durabilecek bazı kodlar bulun. Bunu kendi dosyalarına taşıyın ve bu nedenle tüm dallarda kendi sınıfına taşıyın. Kararlı olduğu için, bir şubeden diğerine geçişi birleştirdiğinizde, orijinal olarak oluşturuldukları dosyadan farklı bir dosyaya uygulanması gereken (çok) "garip" birleşmeye neden olmaz. Tekrar et.

  2. Dosyada sadece az sayıda dal için geçerli olan ve tek başına durabilecek bazı kodları bulun. Az sayıda dal nedeniyle hızlı değişip değişmediği önemli değildir. Bunu kendi sınıflarına ve dosyalarına taşıyın. Tekrar et.

Yani, her yerde aynı olan koddan ve belirli dallara özgü koddan kurtulduk.

Bu sizi kötü yönetilen kodun bir çekirdeği ile bırakır - her yerde gereklidir, ancak her dalda farklıdır (ve / veya sürekli olarak değişir, böylece bazı dallar diğerlerinin arkasında çalışır) ve yine de tek bir dosyada başarısız dallar arasında birleşmeye çalışıyor. Şunu yapmayı kes. Dosyayı kalıcı olarak , belki de her dalda yeniden adlandırarak dallayın. Artık "ana" değil, "X yapılandırması için ana". Tamam, böylece aynı değişikliği birleştirerek birden çok şubeye uygulama yeteneğini kaybedersiniz, ancak bu her durumda birleştirmenin çok iyi çalışmadığı kodun çekirdeğidir. Birleşmeleri yine de çakışmalarla başa çıkmak için elle yönetmeniz gerekiyorsa, bunları her bir şubeye bağımsız olarak manuel olarak uygulamak kayıp olmaz.

Sanırım SCC'nin önemli olmadığını söylemek yanlış, çünkü git'in birleştirme yetenekleri muhtemelen kullandığın birleştirme aracından daha iyi. Temel sorun, "birleştirme zor" farklı SCC'ler için farklı zamanlarda ortaya çıkar. Ancak, SCC'leri değiştiremezsiniz, bu nedenle sorun muhtemelen önemsizdir.


Birleşmeye gelince: GIT'e baktım ve SVN'ye baktım ve Perforce'a baktım ve size hiçbir yerde gördüğüm hiçbir şeyin yaptığımız şey için AccuRev + Araxis'i yendiğini söyleyeyim. :-) (GIT bunu yapabilirse de [ stackoverflow.com/questions/1728922/… ] ve AccuRev yapamaz - herkes bunun birleşme veya tarih analizinin bir parçası olup olmadığına kendisi karar vermelidir.)
Martin Ba

Yeterince adil - belki zaten mevcut en iyi araca sahipsiniz. Git'in X dalında A Dosyasında, Y dalında B Dosyasında meydana gelen bir değişikliği birleştirme becerisi, dallı dosyaları bölmeyi kolaylaştırmak zorundadır, ancak muhtemelen kullandığınız sistemin sizin gibi avantajları vardır. Her neyse, sadece SCC'nin burada bir fark yarattığını söyleyerek git'e geçmenizi önermiyorum, ama buna rağmen bunun indirimli olabileceğini kabul ediyorum :-)
Steve Jessop

129

Birleştirme, gelecekte 30000 LOC dosyası alacağınız kadar büyük bir kabus olmayacak. Yani:

  1. Bu dosyaya daha fazla kod eklemeyi durdurun.
  2. Böl onu.

Yeniden düzenleme işlemi sırasında kodlamayı durduramıyorsanız, bu büyük dosyayı en azından ona daha fazla kod eklemeden bir süre olduğu gibi bırakabilirsiniz : bir "ana sınıf" içerdiği için miras alabilir ve devralınan sınıfı saklayabilirsiniz ( es) birkaç yeni küçük ve iyi tasarlanmış dosyada aşırı yüklenmiş işlevlerle.


@Martin: Neyse ki dosyanızı buraya yapıştırmadınız, bu yüzden yapısı hakkında hiçbir fikrim yok. Ancak genel fikir mantıklı bölümlere ayırmaktır. Bu tür mantıksal parçalar, "ana sınıfınızdan" fonksiyon grupları içerebilir veya birkaç yardımcı sınıfta bölebilirsiniz.
Kirill V. Lyadvinsky

3
10 bakım sürümü ve birçok aktif geliştirici ile dosyanın yeterince uzun süre dondurulamaması olası değildir.
Kobi

9
@Martin, sen hile yapacağını GOF desenleri bir çift, tek sahip Cephe mainmodule.cpp fonksiyonlarını haritalar, alternatif paketi oluşturmak (aşağıda tavsiye ettik) Komut sınıfları bir işleve Her haritanın o / mainmodule.app özelliği. (
Cevabımda

2
Evet tamamen katılıyorum, bir noktada ona kod eklemeyi bırakmak zorundasınız ya da sonunda 30k, 40k, 50k olacak, kaboom ana modülü sadece seg faulted. :-)
Chris

67

Bana birtakım kod kokularıyla karşılaşmış gibi geliyor. Her şeyden önce ana sınıf açık / kapalı prensibini ihlal ediyor gibi görünüyor . Aynı zamanda çok fazla sorumluluk üstlendiği anlaşılıyor . Bu nedenle kodun olması gerekenden daha kırılgan olduğunu varsayabilirim.

Yeniden düzenleme sonrasında izlenebilirlik konusundaki endişelerinizi anlayabilsem de, bu sınıfın sürdürülmesinin ve geliştirilmesinin oldukça zor olduğunu ve yaptığınız değişikliklerin yan etkilere neden olacağını umuyorum. Bunların maliyetinin sınıfı yeniden düzenleme maliyetinden daha ağır bastığını varsayabilirim.

Her durumda, kod kokuları sadece zamanla daha da kötüleşeceğinden, en azından bir noktada bunların maliyeti yeniden düzenleme maliyetinden daha ağır basacaktır. Açıklamanızdan, devrilme noktasını geçtiğinizi varsayacağım.

Yeniden düzenleme küçük adımlarla yapılmalıdır. Mümkünse, herhangi bir şeyi yeniden düzenlemeden önce mevcut davranışı doğrulamak için otomatik testler ekleyin . Daha sonra, yalıtılmış işlevselliğin küçük alanlarını seçin ve sorumluluğu devretmek için bunları tür olarak çıkarın.

Her durumda, büyük bir proje gibi geliyor, çok iyi şanslar :)


18
Çok fazla kokuyor: Blob anti-paterni da evde gibi kokuyor ... en.wikipedia.org/wiki/God_object . En sevdiği yemek spagetti kodu: en.wikipedia.org/wiki/Spaghetti_code :-)
jdehaan

@jdehaan: Bu konuda diplomatik olmaya çalışıyordum :)
Brian Rasmussen

+1 Benden de, testler olmadan yazdığım karmaşık koda bile dokunmaya cesaret edemiyorum.
Danny Thomas

49

Bu tür sorunlara hayal ettiğim tek çözüm şöyle. Açıklanan yöntemle gerçek kazanç, evrimlerin ilerlemesidir. Burada devrimler yok, aksi takdirde çok hızlı bir şekilde başınız derde girecek.

Orijinal ana sınıfın üstüne yeni bir cpp sınıfı ekleyin. Şimdilik, temel olarak tüm çağrıları mevcut ana sınıfa yönlendirecek, ancak bu yeni sınıfın API'sını mümkün olduğunca açık ve özlü hale getirmeyi amaçlıyor.

Bu yapıldıktan sonra, yeni sınıflara yeni işlevler ekleme olanağına sahip olursunuz.

Mevcut işlevselliklere gelince, yeterince istikrarlı hale geldikçe bunları yeni sınıflarda aşamalı olarak taşımalısınız. Bu kod parçası için SCC yardımını kaybedeceksiniz, ancak bu konuda yapılabilecek çok şey yok. Sadece doğru zamanlamayı seçin.

Bunun mükemmel olmadığını biliyorum, ancak umarım yardımcı olabilir ve süreç ihtiyaçlarınıza göre uyarlanmalıdır!

Ek bilgi

Git'in bir dosyadan diğerine kod parçalarını takip edebilen bir SCC olduğunu unutmayın. Bu konuda iyi şeyler duydum, bu yüzden işinizi aşamalı olarak hareket ettirirken yardımcı olabilir.

Git, doğru anlarsam, kod dosyalarının parçalarını temsil eden lekeler kavramı üzerine inşa edilmiştir. Bu parçaları farklı dosyalarda hareket ettirdiğinizde Git onları değiştirseniz bile bulur. Aşağıdaki yorumlarda bahsedilen Linus Torvalds'ın videosu dışında, bu konuda net bir şey bulamadım.


Üzerinde bir başvuru nasıl GIT bunu yapmaz / sen GIT ile bunu nasıl en hoş olurdu.
Martin Ba

@ Martin Git otomatik olarak yapar.
Matthew

4
@Martin: Git otomatik olarak yapar - dosyaları izlemediği için içeriği izler. Git'te sadece "bir dosyanın tarihini almak" daha zordur.
Arafangion

1
@Martin youtube.com/watch?v=4XpnKHJAok8 Torvalds'ın git hakkında konuştuğu bir konuşma. Daha sonra konuşmada bahseder.
Matthew

6
@Martin, şu soruya bakın: stackoverflow.com/questions/1728922/…
Benjol

30

Konfüçyüs diyor ki: "delikten çıkmanın ilk adımı delik kazmayı durdurmaktır."


25

Tahmin edeyim: Farklı özellik kümelerine sahip on müşteri ve "özelleştirmeyi" teşvik eden bir satış yöneticisi? Daha önce böyle ürünler üzerinde çalıştım. Aslında aynı sorunu yaşadık.

Muazzam bir dosyaya sahip olmanın sorun olduğunu biliyorsunuz, ancak daha da fazla sorun "güncel" tutmanız gereken on versiyon. Bu çoklu bakımdır. SCC bunu kolaylaştırabilir, ancak doğru yapamaz.

Dosyayı parçalara ayırmaya çalışmadan önce, tüm kodu bir kerede görebilmeniz ve şekillendirebilmeniz için on dalı birbiriyle senkronize hale getirmeniz gerekir. Her iki dalı da aynı ana kod dosyasına karşı test ederek bu bir dalı birer birer yapabilirsiniz. Özel davranışı zorlamak için #ifdef ve arkadaşlarını kullanabilirsiniz, ancak tanımlı sabitlere karşı normal if / else kullanmak mümkün olduğunca iyidir. Bu şekilde, derleyiciniz tüm türleri doğrular ve büyük olasılıkla "ölü" nesne kodunu ortadan kaldırır. (Yine de ölü kodla ilgili uyarıyı kapatmak isteyebilirsiniz.)

Bu dosyanın tüm şubeler tarafından örtülü olarak paylaşılan tek bir sürümü olduğunda, geleneksel yeniden düzenleme yöntemlerine başlamak daha kolaydır.

#İfdefs, etkilenen kodun yalnızca diğer şube başına özelleştirmeler bağlamında anlamlı olduğu bölümler için daha iyidir. Bunların aynı şube birleştirme şeması için de bir fırsat sunduğunu iddia edebilir, ancak domuz gibi gitmez. Her seferinde devasa bir proje lütfen.

Kısa vadede, dosya büyüyor gibi görünecektir. Tamamdır. Yaptığınız şey, birlikte olması gereken şeyleri bir araya getirmektir. Daha sonra, sürümden bağımsız olarak açıkça aynı olan alanları görmeye başlayacaksınız; bunlar yalnız bırakılabilir veya istendiğinde yeniden düzenlenebilir. Diğer alanlar sürüme bağlı olarak açıkça farklılık gösterecektir. Bu durumda birkaç seçeneğiniz var. Bir yöntem, farklılıkları sürüm başına strateji nesnelerine devretmektir. Bir diğeri, ortak bir soyut sınıftan istemci sürümleri türetmektir. Ancak, bu dallardan hiçbiri, farklı dallarda on gelişim ipucuna sahip olduğunuz sürece mümkün değildir.


2
Hedefin yazılımın bir sürümüne sahip olması gerektiğine katılıyorum, ancak yapılandırma dosyalarını (çalışma zamanı) kullanmak ve zaman custumization derlemek daha iyi olmaz
Esben Skov Pedersen

Veya her müşterinin yapısı için "yapılandırma sınıfları" bile.
tc.

Derleme zamanı veya çalışma zamanı yapılandırmasının işlevsel olarak alakasız olduğunu düşünüyorum, ancak olasılıkları sınırlamak istemiyorum. Derleme zamanı yapılandırması, istemcinin ek özellikleri etkinleştirmek için bir yapılandırma dosyasıyla hacklenememesi avantajına sahiptir, çünkü tüm yapılandırmayı kaynak ağacına konuşlandırılabilir "metin nesnesi" kodu yerine koyar. Kapak tarafı, çalışma zamanı ise AlternateHardAndSoftLayers'a yönelmenizdir.
Ian

22

Bunun sorununuzu çözüp çözmediğini bilmiyorum, ancak yapmak istediğinizi dosyanın içeriğini birbirinden bağımsız küçük dosyalara (özetlenmiş) geçirmektir. Ayrıca aldığım yazılımın yaklaşık 10 farklı sürümü var ve hepsini karıştırmadan hepsini desteklemeniz gerekiyor.

Her şeyden önce , bu kolay ve beyin fırtınası birkaç dakika içinde kendini çözecek hiçbir yolu yoktur . Dosyanıza bağlı işlevlerin tümü uygulamanız için hayati önem taşır ve bunları kesmek ve diğer dosyalara taşımak sorununuzu kurtarmaz.

Sadece bu seçeneklere sahip olduğunuzu düşünüyorum:

  1. Göç etmeyin ve sahip olduklarınızla kalmayın. Muhtemelen işinizi bırakın ve iyi bir tasarıma sahip ciddi yazılımlar üzerinde çalışmaya başlayın. Bir veya iki çarpışmadan kurtulmak için yeterli paraya sahip uzun süreli bir proje üzerinde çalışıyorsanız, aşırı programlama her zaman en iyi çözüm değildir.

  2. Dosyanızı bölündükten sonra nasıl görmek istediğinizi gösteren bir düzen hazırlayın. Gerekli dosyaları oluşturun ve uygulamanıza entegre edin. Ek bir parametre almak için işlevleri yeniden adlandırın veya aşırı yükleyin (belki sadece basit bir boole?). Kodunuz üzerinde çalışmanız gerektiğinde, üzerinde çalışmanız gereken işlevleri yeni dosyaya taşıyın ve eski işlevlerin işlev çağrılarını yeni işlevlerle eşleyin. Hala ana dosyanızın bu şekilde olması ve tam olarak ne zaman dış kaynak kullanıldığını bildiğiniz belirli bir işlev söz konusu olduğunda, yapılan değişiklikleri görebilmeniz gerekir.

  3. İş arkadaşlarınızı, iş akışının abartıldığı ve ciddi iş yapmak için uygulamanın bazı bölümlerini yeniden yazmanız gerektiği konusunda iyi bir pasta ile ikna etmeye çalışın.


19

Tam olarak bu sorun "Eski Kodla Etkili Çalışma" kitabının ( http://www.amazon.com/Working-Efftively-Legacy-Michael-Feathers/dp/0131177052 ) bölümlerinden birinde ele alınmıştır .


informit.com/store/product.aspx?isbn=0131177052 bu kitabın TOC'sini (ve 2 örnek bölümü) görmeyi mümkün kılar. Bölüm 20 ne kadar sürer? (Ne kadar yararlı olabileceğini anlamak için.)
Martin Ba

17
bölüm 20 10.000 satır uzunluğundadır, ancak yazarın bunu sindirilebilir parçalara nasıl bölebileceğini araştırmaktadır ... 8)
Tony Delroy

1
Yaklaşık 23 sayfa, ancak 14 resim. Sanırım onu ​​almalısın, ne yapacağına karar vermeye çalışırken kendinden çok daha güvende hissedeceksin.
Emile Vrijdags

Sorun için mükemmel bir kitap, ancak yaptığı öneriler (ve bu konudaki diğer öneriler) ortak bir gereksinimi paylaşıyor: bu dosyayı tüm şubeleriniz için yeniden düzenlemek istiyorsanız, bunu yapmanın tek yolu tüm şubeler için dosyalama ve ilk yapısal değişiklikleri yapmak. Bunun hiçbir yolu yok. Kitap, yinelenen yöntemler oluşturarak ve çağrıları devrederek alt sınıfları otomatik olarak yeniden düzenleme desteği olmadan güvenli bir şekilde çıkarmak için yinelemeli bir yaklaşımı özetlemektedir, ancak dosyaları değiştiremezseniz bunların tümü tartışmalıdır.
Dan Bryant

2
@Martin, kitap mükemmel ama şu anda bulunduğunuz yerden oldukça zor olabilecek test, refactor, test döngüsüne oldukça fazla güveniyor. Benzer bir durumda bulundum ve bu kitap bulduğum en yararlı kitap oldu. Sahip olduğunuz çirkin sorun için iyi öneriler var. Ancak resme bir tür test koşumu alamazsanız, dünyadaki tüm yeniden düzenleme önerileri size yardımcı olmaz.

14

Ben mainmodule.cpp API noktaları eşleme komut sınıfları kümesi oluşturmak en iyi olacağını düşünüyorum .

Yerleştirildikten sonra, komut sınıfları aracılığıyla bu API noktalarına erişmek için mevcut kod tabanını yeniden düzenlemeniz gerekir, işiniz bittiğinde, her komutun uygulamasını yeni bir sınıf yapısına yeniden aktarabilirsiniz.

Tabii ki, 11 KLOC'luk tek bir sınıfla, içerideki kod muhtemelen yüksek derecede birleşmiş ve kırılgandır, ancak bireysel komut sınıfları oluşturmak, diğer proxy / cephe stratejilerinden çok daha fazla yardımcı olacaktır.

Görevi kıskanmıyorum, ama zaman geçtikçe bu sorun sadece ele alınmazsa daha da kötüleşecek.

Güncelleme

Komuta modelinin bir Cepheye tercih edilebilir olduğunu öneririm.

(Nispeten) monolitik bir Cephe üzerinde birçok farklı Komuta sınıfının korunması / düzenlenmesi tercih edilir. Tek bir Cepheyi 11 KLOC dosyasına eşlemek, muhtemelen birkaç farklı gruba ayrılmalıdır.

Neden bu cephe gruplarını bulmaya çalışıyorsunuz? Komut deseni ile bu küçük sınıfları organik olarak gruplandırabilir ve organize edebilirsiniz, böylece çok daha fazla esnekliğe sahip olursunuz.

Tabii ki, her iki seçenek de tek 11 KLOC ve büyüyen dosyadan daha iyidir.


Aynı fikirle, önerdiğim çözümün alternatifini + 1'leyin: büyük sorunu küçük sorunlara ayırmak için API'yı değiştirin.
Benoît

13

Önemli bir öneri: Yeniden düzenleme ve hata düzeltmelerini karıştırmayın. İstediğiniz, programınızın , kaynak kodun farklı olması dışında, önceki sürümle aynı olan bir Sürümüdür .

Bir yol, en az büyük işlevi / parçayı kendi dosyasına bölmeye başlamak olabilir ve daha sonra ya bir başlık içerir (böylece main.cpp'yi #includes listesine çevirerek, kendi başına bir kod kokusu geliyor * Ben değilim C ++ Guru olsa da), ancak en azından şimdi dosyalara bölünmüş).

Daha sonra tüm bakım sürümlerini "new" main.cpp'ye veya yapınız ne olursa olsun değiştirmeyi deneyebilirsiniz. Tekrar: Bunları izlemek cehennem gibi kafa karıştırıcı olduğu için başka değişiklik veya Hata Düzeltmesi yok.

Başka bir şey: Her şeyi tek seferde yeniden düzenleme konusunda büyük bir geçiş yapmak isteyebileceğiniz kadar çiğneyebileceğinizden daha fazla ısırırsınız. Belki sadece bir veya iki "parça" seçin, tüm sürümlere ekleyin, ardından müşteriniz için biraz daha değer katın (sonuçta, Refactoring doğrudan değer katmaz, bu yüzden haklı olması gereken bir maliyettir) ve sonra başka bir tane seçin bir veya iki parça.

Açıkçası bu, bölünmüş dosyaları kullanmak ve sadece main.cpp'e her zaman yeni şeyler eklemek için takımdaki bazı disiplinleri gerektirir, ancak yine de, büyük bir refactor yapmaya çalışmak en iyi eylem yolu olmayabilir.


1
Faktoring için +1 ve tekrar dahil edin. Bunu 10 şube için de yapsaydınız (orada iş biti, ancak yönetilebilir), yine de tüm dallarınızda değişiklik yayınlamakla ilgili başka bir probleminiz olacaktı, ancak bu sorun olmazdı. t genişletilmiş (zorunlu olarak). Çirkin mi? Evet hala öyle, ama soruna küçük bir rasyonellik getirebilir. Gerçekten çok büyük bir ürün için birkaç yıl bakım ve servis yaparak, bakımın çok fazla acı içerdiğini biliyorum. En azından ondan öğrenin ve başkaları için eğitici bir hikaye olarak hizmet edin.
Jay

10

Rofl, bu bana eski işimi hatırlatıyor. Görünüşe göre, katılmadan önce her şey büyük bir dosyanın içinde (ayrıca C ++). Daha sonra (üçünü de kullanarak tamamen rastgele noktalarda) üçe (hala devasa dosyalara) böldüler. Bu yazılımın kalitesi, beklediğiniz gibi korkunçtu. Proje toplamda 40 bin LOC idi. (neredeyse hiç yorum yok, ancak LOTS yinelenen kod içeriyor)

Sonunda projeyi tamamen yeniden yazdım. Projenin en kötü kısmını sıfırdan başlayarak başladım. Elbette bu yeni bölümle geri kalanlar arasında olası (küçük) bir arayüz vardı. Sonra bu parçayı eski projeye ekledim. Gerekli arayüzü oluşturmak için eski kodu refactor etmedim, ama sadece değiştirdim. Sonra eski kodu yeniden yazarak oradan küçük adımlar attım.

Bunun yaklaşık yarım yıl sürdüğünü ve bu süre zarfında hata düzeltmelerinin yanında eski kod tabanının gelişmediğini söylemeliyim.


Düzenle:

Boyutu yaklaşık 40k LOC kaldı ama yeni uygulama ilk sürümünde 8 yaşındaki yazılımdan çok daha fazla özellik ve muhtemelen daha az hata içeriyordu. Yeniden yazmanın bir nedeni de yeni özelliklere ihtiyaç duymamız ve bunları eski kodun içine sokmamız neredeyse imkansızdı.

Yazılım gömülü bir sistem, etiket yazıcısı içindi.

Eklemem gereken bir diğer nokta da teoride projenin C ++ olduğu. Ama hiç OO değildi, C olabilirdi. Yeni versiyon nesne yönelimli idi.


9
Ben refactoring hakkında konuda "sıfırdan" her duyuyorum Bir yavru kedi öldürmek!
Kugel

Ben çok benzer sondaj bir durumdaydım, ancak kavramak zorunda kaldım ana program döngüsü sadece ~ 9000 LOC oldu. Ve bu yeterince kötüydü.
AndyUK

8

Tamam, çoğunlukla üretim kodunun yeniden yazılması için API başlangıç ​​olarak kötü bir fikirdir. İki şey olması gerekiyor.

Birincisi, ekibinizin bu dosyanın mevcut üretim sürümünde bir kod dondurma yapmaya karar vermesi gerekir.

İkincisi, bu üretim sürümünü almanız ve büyük dosyayı bölmek için önişleme direktiflerini kullanarak yapıları yöneten bir şube oluşturmanız gerekir. Derlemeyi SADECE önişlemci yönergeleri (#ifdefs, #includes, #endifs) kullanarak bölmek API'yi yeniden kodlamaktan daha kolaydır. SLA'larınız ve sürekli desteğiniz için kesinlikle daha kolay.

Burada, sınıf içindeki belirli bir alt sistemle ilgili işlevleri kesebilir ve bunları mainloop_foostuff.cpp diyelim bir dosyaya koyabilir ve onu doğru konumda mainloop.cpp'e ekleyebilirsiniz.

VEYA

Daha fazla zaman alan ancak sağlam bir yol, şeylerin nasıl dahil edileceğine dair iki yönlü dolaylı bir iç bağımlılık yapısı tasarlamak olacaktır. Bu, işleri bölmenize ve eş-bağımlılıklarla ilgilenmenize izin verecektir. Bu yaklaşımın konumsal kodlama gerektirdiğini ve bu nedenle uygun yorumlarla birleştirilmesi gerektiğini unutmayın.

Bu yaklaşım, hangi varyantı derlediğinize bağlı olarak kullanılan bileşenleri içerir.

Temel yapı, mainclass.cpp dosyanızın, aşağıdaki gibi bir ifade bloğundan sonra MainClassComponents.cpp adlı yeni bir dosya içermesidir:

#if VARIANT == 1
#  define Uses_Component_1
#  define Uses_Component_2
#elif VARIANT == 2
#  define Uses_Component_1
#  define Uses_Component_3
#  define Uses_Component_6
...

#endif

#include "MainClassComponents.cpp"

MainClassComponents.cpp dosyasının birincil yapısı, aşağıdaki gibi alt bileşenler içindeki bağımlılıkları çözmek için orada olacaktır:

#ifndef _MainClassComponents_cpp
#define _MainClassComponents_cpp

/* dependencies declarations */

#if defined(Activate_Component_1) 
#define _REQUIRES_COMPONENT_1
#define _REQUIRES_COMPONENT_3 /* you also need component 3 for component 1 */
#endif

#if defined(Activate_Component_2)
#define _REQUIRES_COMPONENT_2
#define _REQUIRES_COMPONENT_15 /* you also need component 15 for this component  */
#endif

/* later on in the header */

#ifdef _REQUIRES_COMPONENT_1
#include "component_1.cpp"
#endif

#ifdef _REQUIRES_COMPONENT_2
#include "component_2.cpp"
#endif

#ifdef _REQUIRES_COMPONENT_3
#include "component_3.cpp"
#endif


#endif /* _MainClassComponents_h  */

Ve şimdi her bileşen için bir component_xx.cpp dosyası oluşturursunuz.

Tabii ki sayıları kullanıyorum ama kodunuza göre daha mantıklı bir şey kullanmalısınız.

Önişlemci kullanmak, üretimde kabus olan API değişiklikleri hakkında endişelenmenize gerek kalmadan işleri bölmenizi sağlar.

Üretimi yerine getirdikten sonra yeniden tasarım üzerinde çalışabilirsiniz.


Bu, başlangıçta acı veren deneyimin sonuçlarına benziyor.
JBRWilkinson

Aslında, Borland C ++ derleyicilerinde başlık dosyalarını yönetmek için Pascal stili Kullanımları taklit etmek için kullanılan bir tekniktir. Özellikle Metin tabanlı Pencereleme sisteminin ilk bağlantı noktasını yaptıkları zaman.
Elf King

8

Acınızı anlıyorum :) Bu tür birkaç projede de bulundum ve bu hoş değil. Bunun kolay bir cevabı yok.

Sizin için işe yarayabilecek bir yaklaşım, tüm işlevlerde güvenli korumalar eklemeye başlamaktır, yani, argümanları, yöntemlerde ön / son koşullarını kontrol etmek, ardından sonunda kaynakların mevcut işlevselliğini yakalamak için birim testleri eklemek. Bunu yaptıktan sonra, kodu yeniden faktörlendirmek için daha iyi bir donanıma sahipsiniz, çünkü bir şeyleri unuttuysanız sizi uyaran hatalar ve hatalar olacak.

Bazen, yeniden düzenlemenin faydadan daha fazla acı getirebileceği zamanlar olsa da. Daha sonra orijinal projeden ve sahte bir bakım durumunda bırakmak ve sıfırdan başlamak ve daha sonra canavara işlevselliği kademeli olarak eklemek daha iyi olabilir.


4

Dosya boyutunu küçültmekle değil, sınıf boyutunu küçültmekle ilgilenmelisiniz. Neredeyse aynı geliyor, ancak soruna farklı bir açıdan bakmanızı sağlıyor (@Brian Rasmussen'in önerdiği gibi, sınıfınızın birçok sorumluluk taşıyor gibi görünüyor).


Her zaman olduğu gibi, aşağı oy için bir açıklama almak istiyorum.
Björn Pollex

4

Sahip olduğunuz klasik bir örnek , blob adı verilen bilinen bir tasarım antipatternidir . Burada işaret ettiğim makaleyi okumak için biraz zaman ayırın ve belki de yararlı bir şeyler bulabilirsiniz. Ayrıca, bu proje göründüğü kadar büyükse, kontrol edemediğiniz koda dönüşmeyi önlemek için bazı tasarımlar düşünmelisiniz.


4

Bu büyük sorunun cevabı değil, belirli bir parçasına teorik bir çözümdür:

  • Büyük dosyayı alt dosyalara bölmek istediğiniz yeri bulun. Bu noktaların her birine bazı özel biçimlerde yorumlar ekleyin.

  • Dosyayı bu noktalarda alt dosyalara ayıracak oldukça önemsiz bir komut dosyası yazın. (Belki de özel yorumlar betiğin nasıl bölündüğüne ilişkin talimatlar olarak kullanabileceği gömülü dosya adlarına sahiptir.) Bölünmenin bir parçası olarak yorumları korumalıdır.

  • Komut dosyasını çalıştırın. Orijinal dosyayı silin.

  • Bir daldan birleştirmeniz gerektiğinde, önce parçaları birleştirerek büyük dosyayı yeniden oluşturun, birleştirmeyi yapın ve sonra yeniden bölün.

Ayrıca, SCC dosya geçmişini korumak istiyorsanız, bunu yapmanın en iyi yolunun, kaynak kontrol sisteminize tek tek parça dosyalarının orijinalin kopyaları olduğunu söylemektir. Daha sonra bu dosyada tutulan bölümlerin geçmişini koruyacak, ancak elbette büyük parçaların "silindiğini" de kaydedecektir.


4

Çok fazla tehlike olmadan bölmenin bir yolu, tüm hat değişikliklerine tarihi bir bakış atmak olacaktır. Diğerlerinden daha kararlı bazı işlevler var mı? Sıcak noktalar değişecek.

Bir satır birkaç yıl içinde değiştirilmediyse, çok fazla endişe duymadan muhtemelen başka bir dosyaya taşıyabilirsiniz. Belirli bir satıra dokunan son revizyonla açıklamalı kaynağa bir göz atacağım ve çekebileceğiniz herhangi bir fonksiyon olup olmadığını göreceğim.


Bence başkaları da benzer şeyler önerdi. Bu kısa ve nokta ve bence bu orijinal sorun için geçerli bir başlangıç ​​noktası olabilir.
Martin Ba

3

Vay, kulağa hoş geliyor. Bence patronunuza açıklamak, canavarı yeniden düşünmek için çok zamana ihtiyacınız olduğunu denemeye değer. Eğer kabul etmezse, bırakmak bir seçenektir.

Her neyse, önerdiğim temelde tüm uygulamayı atmak ve yeni modüller halinde yeniden gruplandırmak, bu "küresel hizmetler" diyelim. "Ana modül" yalnızca bu hizmetlere yönlendirilir ve yazdığınız HERHANGİ yeni kod "ana modül" yerine bunları kullanır. Bu makul bir süre içinde mümkün olmalıdır (çoğunlukla kopyalayıp yapıştırdığı için), mevcut kodu kırmazsınız ve her seferinde bir bakım sürümü yapabilirsiniz. Ve hala zamanınız varsa, global hizmetleri kullanmak için eski bağımlı modüllerin tümünü yeniden düzenleyerek geçirebilirsiniz.


3

Sempatilerim - önceki işimde, uğraşmak zorunda olduğunuzdan birkaç kat daha büyük bir dosya ile benzer bir durumla karşılaştım. Çözüm şuydu:

  1. Söz konusu programdaki işlevi kapsamlı bir şekilde test etmek için kod yazın. Görünüşe göre elinizde zaten bu yok ...
  2. Bir yardımcı / yardımcı program sınıfına çıkarılabilecek bazı kodları tanımlayın. Büyük olmanıza gerek yok, sadece 'ana' sınıfınızın bir parçası olmayan bir şey.
  3. 2.'de tanımlanan kodu ayrı bir sınıfa yeniden yansıtın.
  4. Hiçbir şeyin bozulmadığından emin olmak için testlerinizi tekrar çalıştırın.
  5. Zamanınız olduğunda, 2. gidin ve kodu yönetilebilir hale getirmek için gerektiği kadar tekrarlayın.

3. adımda oluşturduğunuz sınıflar yinelemeleri, yeni silme işlevlerine uygun olan daha fazla kodu emecek şekilde büyüyecektir.

Ayrıca ekleyebilirim:

0: Michael Feathers'ın kitabını satın al eski kodla çalışma

Ne yazık ki bu tür işler çok yaygındır, ancak benim deneyimim, çalışmayı sürdürürken çalışmayı ancak korkunç kodu kademeli olarak daha az korkunç hale getirmenin büyük bir değeri olduğudur.


2

Tüm uygulamayı daha mantıklı bir şekilde yeniden yazmanın yollarını düşünün. Belki de fikrinizin mümkün olup olmadığını görmek için küçük bir bölümünü prototip olarak yeniden yazabilirsiniz.

Uygulanabilir bir çözüm belirlediyseniz, uygulamayı yeniden düzenleyin.

Daha rasyonel bir mimari üretmek için yapılan tüm girişimler başarısız olursa, en azından çözümün muhtemelen programın işlevselliğini yeniden tanımlamakta olduğunu bilirsiniz.


+1 - kendi zamanında yeniden yaz, aksi halde birisi kuklalarını tükürebilir.
Jon Black

2

0.05 eurocents'im:

Tüm karmaşayı yeniden tasarlayın, teknik ve iş gereksinimlerini dikkate alarak alt sistemlere ayırın (= her biri için potansiyel olarak farklı kod tabanına sahip birçok paralel bakım izi, açıkçası yüksek değiştirilebilirliğe ihtiyaç vardır, vb.).

Alt sistemlere ayrılırken, en çok değişen yerleri analiz edin ve bunları değişmeyen parçalardan ayırın. Bu size sorunlu noktaları göstermelidir. Modül API'si sağlam tutulabilecek ve her zaman BC'yi kırmanıza gerek kalmayacak şekilde en çok değişen parçaları kendi modüllerine (örn. Dll) ayırın. Bu şekilde, çekirdeği değiştirmeden modülün farklı sürümlerini gerekirse farklı bakım dalları için dağıtabilirsiniz.

Yeniden tasarımın muhtemelen ayrı bir proje olması gerekecektir, hareketli bir hedefe yapmaya çalışmak işe yaramayacaktır.

Kaynak kod geçmişine gelince, bence: yeni kod için unutun. Ancak geçmişi bir yerde saklayın, böylece gerekirse kontrol edebilirsiniz. Bahse girerim, başlangıçtan sonra buna çok fazla ihtiyacınız olmayacak.

Büyük olasılıkla bu proje için yönetim satın almanız gerekir. Belki daha hızlı geliştirme süresi, daha az hata, daha kolay bakım ve daha az genel kaos ile tartışabilirsiniz. "Kritik yazılım varlıklarımızın proaktif olarak geleceğe dönük olmasını ve bakım uygulanabilirliğini sağlar" çizgisi boyunca bir şeyler

Sorunu en azından bu şekilde ele almaya başladım.


2

Buna yorum ekleyerek başlayın. İşlevlerin nerede çağrıldığına ve bir şeyleri hareket ettirip taşıyamayacağınıza referansla. Bu, işleri hareket ettirebilir. Kodun ne kadar kırılgan olduğunu değerlendirmeniz gerekiyor. Ardından ortak işlevsellik parçalarını bir araya getirin. Her seferinde küçük değişiklikler.



2

Yapmak için yararlı bulduğum bir şey (ve şimdi karşı karşıya olduğum ölçekte olmasa da yapıyorum), yöntemleri sınıf olarak (yöntem nesnesi yeniden düzenleme) ayıklamaktır. Farklı sürümleriniz arasında farklılık gösteren yöntemler, ihtiyacınız olan farklı davranışı sağlamak için ortak bir tabana enjekte edilebilen farklı sınıflar haline gelecektir.


2

Bu cümleyi yayınınızın en ilginç kısmı olarak buldum:

> Dosya, ürünümüzün birkaç (> 10) bakım sürümünde kullanılır ve aktif olarak değiştirilir ve bu nedenle yeniden düzenlenmesi gerçekten zordur

İlk olarak, dallamayı destekleyen bu 10 + bakım sürümlerini geliştirmek için bir kaynak kontrol sistemi kullanmanızı tavsiye ederim.

İkinci olarak, on şube (bakım sürümlerinizin her biri için bir tane) oluştururdum.

Seni zaten kandırdığını hissediyorum! Ancak, kaynak kontrolünüz, özellik eksikliği nedeniyle durumunuz için çalışmıyor veya doğru şekilde kullanılmıyor.

Şimdi üzerinde çalıştığınız şubeye - uygun gördüğünüz gibi yeniden düzenleyin, ürününüzün diğer dokuz dalını rahatsız etmeyeceğiniz konusunda güvende olun.

Main () fonksiyonunuzda çok fazla şey olduğundan biraz endişe ediyorum.

Yazdığım herhangi bir projede, main () işlevini yalnızca bir simülasyon veya uygulama nesnesi gibi temel nesnelerin başlatılmasını gerçekleştirmek için kullanırım - bu sınıflar gerçek çalışmanın devam etmesi gereken yerdir.

Ayrıca program boyunca küresel olarak kullanmak için ana bir uygulama günlüğü nesnesi başlatmak.

Son olarak, ana olarak önişlemci bloklarına yalnızca DEBUG derlemelerinde etkinleştirildiğinden emin olmak için kaçak tespit kodu ekliyorum. Bu main () eklemek istiyorum. Main () kısa olmalı!

Bunu sen söyledin

> Dosya temel olarak programımızın "ana sınıfını" (ana dahili iş gönderimi ve koordinasyonu) içerir

Bu iki görevin iki ayrı nesneye ayrılabileceği anlaşılıyor - bir koordinatör ve bir iş dağıtıcısı.

Bunları böldüğünüzde, "SCC iş akışınızı" bozabilirsiniz, ancak SCC iş akışınıza sıkı sıkıya bağlı kalmak yazılım bakım sorunlarına neden oluyor gibi görünüyor. Şimdi bırakın ve geriye bakmayın, çünkü düzelttiğiniz anda kolay uyumaya başlayacaksınız.

Karar veremiyorsanız, bunun için yöneticinizle diş ve çivi ile savaşın - uygulamanızın yeniden düzenlenmesi gerekir - ve sesleriyle kötü! Cevap için hayır!


Anladığım kadarıyla sorun şu: eğer mermiyi ve refactoru ısırırsanız, artık sürümler arasında yamalar taşıyamazsınız. SCC mükemmel şekilde kurulmuş olabilir.
peterchen

@peterchen - tam olarak sorun. SCC'ler dosya düzeyinde birleşiyor. (3-yollu birleşmeler) Kodu dosyalar arasında hareket ettirirseniz, değiştirilen kod bloklarını bir dosyadan diğerine manuel olarak çevirmeye başlamanız gerekir. (Başka bir yorumda bahsedilen GIT özelliği, anlatabildiğim kadarıyla birleştirmek için değil, tarih için iyidir)
Martin Ba

2

Açıkladığınız gibi, ana sorun önceden bölünmüş vs sonrası bölünmüş, hata düzeltmelerinde birleştirme vb .. Etrafındaki araç. Perl, Ruby, vb. Bir senaryoyu kodlamak için uzun süre ayrılmayacaktır. Gürültüyle başa çıkmak için en kolay olanı yapın:

  • birleştirme öncesinde / sırasında belirli satırları kaldır (örn. korumaları dahil et)
  • gerekirse diff çıkışındaki diğer şeyleri çıkarın

Hatta bir check-in olduğunda, birleştirme çalışır ve tek dosya sürümlerine karşı farklı bir şey hazırlayabilirsiniz.


2
  1. Bu dosyaya ve koda bir daha dokunmayın!
  2. Tedavi etmek sıkışmış bir şey gibidir. Orada kodlanan işlevsellik için bağdaştırıcılar yazmaya başlayın.
  3. Farklı birimlere yeni kod yazın ve sadece canavarın işlevselliğini kapsayan adaptörlerle konuşun.
  4. ... yukarıdakilerden sadece biri mümkün değilse, işten çıkın ve size yeni bir tane alın.

2
+/- 0 - cidden, böyle bir teknik ayrıntıya dayanarak bir işi bırakmanızı önerdiğiniz insanlar nerede yaşıyor?
Martin Ba

1

"Dosya temel olarak programımızın" ana sınıfını "(ana dahili iş dağıtımı ve koordinasyonu) içerir, bu nedenle her özellik eklendiğinde, bu dosyayı ve her büyüdüğünde de etkiler."

Bu büyük ANAHTAR (ki orada olduğunu düşünüyorum) ana bakım sorunu haline gelirse, sözlüğü ve Komut desenini kullanmak için yeniden düzenleyebilir ve mevcut koddan o haritayı dolduran yükleyiciye tüm anahtar mantığını kaldırabilirsiniz, yani:

    // declaration
    std::map<ID, ICommand*> dispatchTable;
    ...

    // populating using some loader
    dispatchTable[id] = concreteCommand;

    ...
    // using
    dispatchTable[id]->Execute();

2
Hayır, aslında büyük bir geçiş yok. Cümle sadece bu karışıklığı tanımlamak için gelebilir en yakın :)
Martin Ba

1

Bir dosyayı bölerken kaynak geçmişini izlemenin en kolay yolunun şöyle olacağını düşünüyorum:

  1. SCM sisteminizin sağladığı geçmişi koruyan kopya komutlarını kullanarak orijinal kaynak kodunun kopyalarını oluşturun. Muhtemelen bu noktada göndermeniz gerekecektir, ancak derleme sisteminize yeni dosyalar hakkında bilgi vermenize gerek yoktur, bu yüzden sorun değil.
  2. Bu kopyalardaki kodu silin. Bu, tuttuğunuz çizgilerin tarihini kırmamalıdır.

"SCM sisteminizin sağladığı geçmişi koruyan kopya komutlarını kullanarak" ... vermediği kötü bir şey
Martin Ba

Çok kötü. Bu tek başına daha modern bir şeye geçmek için iyi bir neden gibi geliyor. :-)
Christopher Creutzig

1

Sanırım bu durumda ne yapacağım mermi biraz ve:

  1. (Mevcut geliştirme sürümüne bağlı olarak) dosyayı nasıl bölmek istediğimi anlayın
  2. Dosyaya bir yönetici kilidi yerleştirin ("Cuma günü 17: 00'den sonra kimse mainmodule.cpp'ye dokunmuyor !!!"
  3. Bu değişikliği uygulayarak uzun hafta sonunuzu, geçerli sürüme kadar ve bu sürüme dahil olmak üzere> 10 bakım sürümünden (en eskiden en yeniye) geçirin.
  4. Mainmodule.cpp yazılımın desteklenen tüm sürümlerinden silin. Bu yeni bir Çağ - artık mainmodule.cpp yok.
  5. Yönetimi, yazılımın birden fazla bakım sürümünü desteklememeniz gerektiğine ikna edin (en azından büyük bir $$$ destek sözleşmesi olmadan). Müşterilerinizin her biri kendi benzersiz sürümüne sahipse .... yeeeeeshhhh. 10+ çatal tutmaya çalışmak yerine derleyici yönergeleri ekliyorum.

Dosyadaki eski değişiklikleri izlemek, ilk check-in yorumunuzda "mainmodule.cpp'den bölün" gibi bir şey söyleyerek çözülür. Son zamanlardaki bir şeye geri dönmeniz gerekiyorsa, çoğu insan değişikliği hatırlayacaktır, bundan 2 yıl sonra, yorum onlara nereye bakacaklarını söyleyecektir. Tabii ki, kodu kimin değiştirdiğine ve nedenine bakmak için 2 yıldan fazla geriye gitmek ne kadar değerli olacak?

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.