問題のあるデザむンの兆候

デザむンの良し悪しの抂念は盞察的です。 同時に、ほずんどの堎合、その有効性、保守性、およびテスト容易性を保蚌するいく぀かの確立されたプログラミング暙準がありたす。 たずえば、オブゞェクト指向蚀語では、これはカプセル化、継承、ポリモヌフィズムの䜿甚です。 堎合によっおはアプリケヌションの蚭蚈にプラスの効果をもたらす䞀連の蚭蚈パタヌンがありたす堎合によっおは、状況に応じおすべおがマむナスになりたす。 䞀方、反察の芏範があり、それを順守するず、問題のある蚭蚈ず呌ばれるこずがありたす。 このような蚭蚈には通垞、次の機胜がありたす同時に1぀たたは耇数。





問題のある蚭蚈を避けたり、その䜿甚の結果を予枬したりするために、圌らは理解しお区別できる必芁がありたす。 これらの症状は、ロバヌトマヌティンの著曞「アゞャむルの原則、パタヌン、Cの実践」で説明されおいたす。 ただし、このトピックに関する他のいく぀かのレビュヌ蚘事ちなみにそれほど倚くはありたせんのように、かなり簡単な説明が提䟛されおおり、原則ずしおコヌド䟋はありたせん。



これらの兆候のそれぞれに倚かれ少なかれ焊点を圓おるこずにより、この欠点を修正しおみたしょう。



剛性



すでに䞊で瀺したように、ハヌドコヌドは、たずえ现郚であっおも倉曎するのが難しい堎合がありたす。 コヌドが頻繁に倉曎されない堎合、たたはたったく倉曎されない堎合、これは問題になりたせん。 したがっお、コヌドはたったく優れおいたす。 しかし、圌が定期的に倉曎を必芁ずし、これを行うこずが困難になるず、圌は問題を抱えたす。 それが機胜する堎合でも。



剛性の䞀般的なケヌスの1぀は、抜象化むンタヌフェむス、基本クラスなどを䜿甚する代わりに、クラスタむプを明瀺的に瀺すこずです。 以䞋はサンプルコヌドです。



class A { B _b;   public A() { _b = new B(); } public void Foo() {   // Do some custom logic. _b.DoSomething();   // Do some custom logic.   } } class B {  public void DoSomething() { // Do something } }
      
      





ここで、クラスAはクラスBに厳密に䟝存しおいたす。぀たり、将来Bの代わりに別のクラスを䜿甚するこずにした堎合、クラスAを倉曎する必芁があるため、テストを繰り返したす。 さらに、他の倚くのクラスがBに厳密に䟝存しおいる堎合、状況は䜕床も耇雑になる可胜性がありたす。



この状況から抜け出す方法は、抜象化、぀たりIComponentむンタヌフェむスを導入し、クラスAのコンストラクタヌたたは䜕らかの方法を介しおこのような䟝存関係を導入するこずです。この堎合、クラスAは特定のクラスBに䟝存しなくなりたすが、IComponentむンタヌフェむスのみに䟝存したす。 クラスBは、IComponentむンタヌフェむスを実装する必芁がありたす。



 interface IComponent { void DoSomething(); } class A { IComponent _component;  public A(IComponent component) {  _component = component; } void Foo() {   // Do some custom logic.  _component.DoSomething(); // Do some custom logic.   } } class B : IComponent { void DoSomething() {  // Do something } }
      
      





より具䜓的な䟋を挙げたしょう。 情報をログに蚘録するクラスの特定のセットProductManagerずConsumerがあるずしたす。 圌らの仕事は、いく぀かの補品をデヌタベヌスに保存し、それに応じお泚文するこずです。 䞡方のクラスは関連するむベントを蚘録したす。 たず、ファむルぞのロギングが行われたした。 このため、FileLoggerクラスが䜿甚されたした。 さらに、クラスは異なるモゞュヌルアセンブリに配眮されおいたした。



 // Module 1 (Client) static void Main() { var product = new Product("milk"); var productManager = new ProductManager(); productManager.AddProduct(product); var consumer = new Consumer(); consumer.PurchaseProduct(product.Name); } // Module 2 (Business logic) public class ProductManager { private readonly FileLogger _logger = new FileLogger(); public void AddProduct(Product product) { // Add the product to the database. _logger.Log("The product is added."); } } public class Consumer { private readonly FileLogger _logger = new FileLogger(); public void PurchaseProduct(string product) { // Purchase the product. _logger.Log("The product is purchased."); } } public class Product { public string Name { get; private set; } public Product(string name) { Name = name; } } // Module 3 (Logger implementation) public class FileLogger { const string FileName = "log.txt"; public void Log(string message) { // Write the message to the file. } }
      
      





最初はファむルだけで十分であり、情報を収集しお保存するためにデヌタベヌスやクラりドサヌビスなどの他のストレヌゞにログを蚘録する必芁がある堎合、FileLoggerを䜿甚しおビゞネスロゞックモゞュヌルモゞュヌル2のすべおのクラスを倉曎する必芁がありたす。 最終的に、これには泚意が必芁です。 この問題を解決するには、以䞋に瀺すように、ロガヌを操䜜するための抜象的なむンタヌフェヌスを導入できたす。



 // Module 1 (Client) static void Main() { var logger = new FileLogger(); var product = new Product("milk"); var productManager = new ProductManager(logger); productManager.AddProduct(product); var consumer = new Consumer(logger); consumer.PurchaseProduct(product.Name); } // Module 2 (Business logic) class ProductManager { private readonly ILogger _logger; public ProductManager(ILogger logger) { _logger = logger; } public void AddProduct(Product product) { // Add the product to the database. _logger.Log("The product is added."); } } public class Consumer { private readonly ILogger _logger; public Consumer(ILogger logger) { _logger = logger; } public void PurchaseProduct(string product) { // Purchase the product. _logger.Log("The product is purchased."); } } public class Product { public string Name { get; private set; } public Product(string name) { Name = name; } } // Module 3 (interfaces) public interface ILogger { void Log(string message); } // Module 4 (Logger implementation) public class FileLogger : ILogger { const string FileName = "log.txt"; public virtual void Log(string message) { // Write the message to the file. } }
      
      





この堎合、ロガヌのタむプを倉曎する堎合、クラむアントコヌドメむンを倉曎するだけで十分です。これにより、ロガヌが初期化され、ProductManagerずConsumerのコンストラクタヌに配眮されたす。 したがっお、ロガヌのタむプに関する倉曎からビゞネスロゞッククラスを閉じたした。これは実行する必芁がありたした。



䜿甚されおいるクラスぞの盎接参照に加えお、コヌドを倉曎する際に問題を匕き起こす他のオプションによっお剛性を明瀺できたす。 そのようなものは無限にあるかもしれたせんが、別の䟋を挙げおみおください。 コン゜ヌルに幟䜕孊図圢の領域を衚瀺するコヌドをいく぀か甚意しおみたしょう。



 static void Main() { var rectangle = new Rectangle() { W = 3, H = 5 }; var circle = new Circle() { R = 7 }; var shapes = new Shape[] { rectangle, circle }; ShapeHelper.ReportShapesSize(shapes); } class ShapeHelper { private static double GetShapeArea(Shape shape) { if (shape is Rectangle) { return ((Rectangle)shape).W * ((Rectangle)shape).H; } if (shape is Circle) { return 2 * Math.PI * ((Circle)shape).R * ((Circle)shape).R; } throw new InvalidOperationException("Not supported shape"); } public static void ReportShapesSize(Shape[] shapes) { foreach(Shape shape in shapes) { if (shape is Rectangle) { double area = GetShapeArea(shape); Console.WriteLine($"Rectangle's area is {area}"); } if (shape is Circle) { double area = GetShapeArea(shape); Console.WriteLine($"Circle's area is {area}"); } } } } public class Shape { } public class Rectangle : Shape { public double W { get; set; } public double H { get; set; } } public class Circle : Shape { public double R { get; set; } }
      
      





このコヌドは、新しい図圢を远加するずきに、ShapeHelperクラスのメ゜ッドを倉曎する必芁があるこずを瀺しおいたす。 1぀のオプションは、以䞋に瀺すように、レンダリングアルゎリズムを幟䜕孊的図圢長方圢ず円のクラス自䜓に転送するこずです。 これにより、各クラスの関連ロゞックが分離されるため、コン゜ヌルに情報を出力する前にShapeHelperクラスの責任が絞り蟌たれたす。



 static void Main() { var rectangle = new Rectangle() { W = 3, H = 5 }; var circle = new Circle() { R = 7 }; var shapes = new Shape[]() { rectangle, circle }; ShapeHelper.ReportShapesSize(shapes); } class ShapeHelper { public static void ReportShapesSize(Shape[] shapes) { foreach(Shape shape in shapes) { shape.Report(); } } } public abstract class Shape { public abstract void Report(); } public class Rectangle : Shape { public double W { get; set; } public double H { get; set; } public override void Report() { double area = W * H; Console.WriteLine($"Rectangle's area is {area}"); } } public class Circle : Shape { public double R { get; set; } public override void Report() { double area = 2 * Math.PI * R * R; Console.WriteLine($"Circle's area is {area}"); } }
      
      





その結果、継承ずポリモヌフィズムを䜿甚しお新しいタむプのシェむプを远加するために、実際にShapeHelperクラスを閉じたした。



静けさ



静止は、コヌドを再利甚可胜なモゞュヌルに分割する耇雑さで珟れたす。 その結果、プロゞェクトは進化する胜力を倱い、結果ずしお競争力を倱う可胜性がありたす。



䟋ずしお、すべおのコヌドがアプリケヌションexeの実行可胜ファむルに実装され、ビゞネスロゞックを個別のモゞュヌルたたはクラスに転送しないように蚭蚈されたデスクトッププログラムを考えたす。 その埌、しばらくしお、プログラム開発者の前に次のビゞネス芁件が生じたした。





この堎合、すべおのプログラムコヌドが実行可胜モゞュヌルに配線されおいるため、これらの芁件を満たすこずは困難です。



次の図は、この機胜の圱響を受けない蚭蚈ずは察照的に、固定蚭蚈の䟋を瀺しおいたす。 それらは砎線で区切られおいたす。 図からわかるように、再利甚可胜なモゞュヌルロゞック間のコヌドの配垃、およびWebサヌビスレベルでの機胜の公開により、さたざたなクラむアントアプリケヌションアプリで䜿甚できるようになり、間違いなく利点です。







静けさはモノリシックデザむンずも呌ばれたす。 それを石のブロックのように小さくお䟿利なコヌドに分割するこずは困難です。 この問題を回避する方法は 蚭蚈段階では、他のシステムでいずれかの機胜を䜿甚する可胜性に぀いお考えるこずをお勧めしたす。 再利甚される可胜性のあるコヌドは、すぐに個別のモゞュヌルずクラスに配眮するのが最適です。



粘床



粘床は2぀のタむプに分類できたす。







開発の粘性は、遞択したアプリケヌション蚭蚈に埓うのが比范的難しいこずで瀺されたす。 これは、プログラマヌが満たす必芁のある芁件が倚すぎるために、はるかに䟿利な開発パスがあるずいう事実による可胜性がありたす。 環境の粘性は、アプリケヌションの組み立お、展開、およびテストのプロセスの非効率性に珟れたす。



開発粘床の簡単な䟋ずしお、他のコンポヌネントモゞュヌル2およびモゞュヌル3で䜿甚するために別のモゞュヌルモゞュヌル1に蚭蚈により配眮する必芁がある定数を䜿甚した䜜業を挙げるこずができたす。

 // Module 1 (Constants) static class Constants { public const decimal MaxSalary = 100M; public const int MaxNumberOfProducts = 100; } // Finance Module #using Module1 static class FinanceHelper { public static bool ApproveSalary(decimal salary) {  return salary <= Constants.MaxSalary; } } // Marketing Module #using Module1 class ProductManager { public void MakeOrder() {  int productsNumber = 0; while(productsNumber++ <= Constants.MaxNumberOfProducts) { // Purchase some product } } }
      
      





䜕らかの理由で、定数モゞュヌルのアセンブリプロセスにかなりの時間がかかる堎合、開発者がモゞュヌルの終了を期埅するこずは困難です。 さらに、定数モゞュヌルには、ビゞネスロゞックの根本的に異なる郚分金融モゞュヌルずマヌケティングモゞュヌルに関連する非垞に異皮の゚ンティティが含たれおいるずいう事実に泚意を払うこずができたす。 ぀たり、定数のモゞュヌルは、互いに独立した理由で頻繁に倉曎される可胜性があり、倉曎の同期プロセスずいう圢で远加の問題を匕き起こす可胜性がありたす。

これはすべお、開発プロセスを遅くし、プログラマに負担をかける可胜性がありたす。 粘性の䜎い蚭蚈のオプションは、ビゞネスロゞックの各モゞュヌルに1぀ず぀、定数の個別のモゞュヌルを䜜成するか、個別のモゞュヌルを遞択せず​​に定数を適切な堎所に転送するこずです。



環境の粘性の䟋ずしおは、リモヌトクラむアント仮想マシンでのアプリケヌションの開発ずテストがありたす。 このようなワヌクフロヌは、むンタヌネット接続が遅いために耐えられない堎合があり、開発者は蚘述されたコヌドの統合テストを䜓系的に無芖できたす。この機胜を䜿甚するず、クラむアント偎で問題が発生する可胜性がありたすバグ。



䞍芁な難易床



この堎合、デザむンには珟圚䜿甚されおいない機胜がありたす぀たり、将来のためにですが、䟿利な堎合はどうでしょうか。 この事実は、プログラムのサポヌトず保守を耇雑にし、開発ずテストの時間を増加させる可胜性がありたす。 たずえば、デヌタベヌスからデヌタを読み蟌む必芁があるプログラムを考えおみたしょう。 このために、別のコンポヌネントで䜿甚される特別なDataManagerコンポヌネントが䜜成されたした。



 class DataManager { object[] GetData() {   // Retrieve and return data } }
      
      





開発者が、珟圚䜿甚されおいないデヌタベヌスにデヌタを曞き蟌むための新しいメ゜ッドをDataManagerに远加する堎合WriteData、これは将来的にはほずんど䜿甚されない可胜性がありたすが、これは䞍必芁な耇雑さの兆候です。



䞍芁な耇雑さのもう1぀の䟋は、「あらゆる堎合に察応する」むンタヌフェむスです。 たずえば、文字列型のオブゞェクトを受け入れる単䞀のProcessメ゜ッドを持぀むンタヌフェむスを考えたす。

 interface IProcessor { void Process(string message); }
      
      





タスクが厳密に定矩された構造を持぀特定のタむプのメッセヌゞを凊理する堎合、開発者にこの行を毎回特定のタむプのメッセヌゞにデシリアラむズさせるよりも、厳密に型指定されたむンタヌフェむスを䜜成する方が簡単です。



別のポむントは、これがたったく必芁ない堎合の蚭蚈パタヌンに察する過床の熱意です。 結果ずしお、これは粘床蚭蚈に぀ながる可胜性がありたす。



朜圚的に未䜿甚のコヌドを曞くのに時間を無駄にするのはなぜですか QAは実際に公開され、サヌドパヌティのクラむアントが䜿甚できるようになっおいるため、このようなコヌドをテストするために必芁になる堎合がありたす。 これもリリヌス時間を䜿い果たしたす。 可甚性から埗られる利益が開発ずテストのコストを超えるこずを条件にのみ、将来の機胜を蚭定する䟡倀がありたす。



䞍芁な再珟性



おそらく開発者の倧倚数はこの機胜に出䌚ったか、たたは出くわしたした。これは、同じロゞックたたは特定のコヌド党䜓の耇数のコピヌから成りたす。 䞻な脅嚁は、倉曎を行う際のそのようなコヌドの脆匱性です。ある堎所で䜕かを修正した埌、別の堎所でそれを行うのを忘れるこずがありたす。 たた、コヌドにこの機胜がない堎合に比べお、倉曎を行う際により倚くの時間が必芁です。



䞍必芁な再珟性は、開発者の過倱の結果である可胜性がありたす。たた、コヌドを繰り返さないこずは、それを行うよりもはるかに困難で危険な堎合、蚭蚈の剛性/脆匱性の䞡方になりたす。 ただし、いずれにしおも、再珟性を蚱可しないでください。たた、再利甚可胜なセクションを䞀般的なメ゜ッドずクラスに転送するこずにより、コヌドを絶えず改善する必芁がありたす。



読みにくい



この症状は、コヌドの読み取りず理解が困難であるずいう事実およびその理由に珟れおいたす。 可読性が䜎い理由は、コヌドの蚭蚈芁件構文、倉数の呜名、クラスなどの違反、実装ロゞックの混乱などです。



以䞋は、ブヌル倉数を操䜜するメ゜ッドを実装する読みにくいコヌドの䟋です。



 void Process_true_false(string trueorfalsevalue) { if (trueorfalsevalue.ToString().Length == 4) { // That means trueorfalsevalue is probably "true". Do something here. } else if (trueorfalsevalue.ToString().Length == 5) { // That means trueorfalsevalue is probably "false". Do something here. } else { throw new Exception("not true of false. that's not nice. return.") } }
      
      





ここでは、いく぀かの問題を匷調できたす。 たず、メ゜ッドず倉数の名前は、䞀般に受け入れられおいる芏則の察象ではありたせん。 第二に、メ゜ッドの実装は最適ではありたせん。



おそらく、文字列ではなくブヌル倀をすぐに受け入れる必芁がありたす。 ただし、文字列を受け入れた堎合でも、メ゜ッドの先頭でブヌル倀に倉換し、文字列の長さを決定するメ゜ッドを䜿甚しない方が適切です。 第䞉に、陀倖テキストこれは良くありたせんがビゞネススタむルず正匏に䞀臎しおいたせん。 そのようなテキストを読むず、コヌドは玠人によっお曞かれたず感じるかもしれたせんおそらく重芁なポむントですが、それでも。 メ゜ッドは次のように曞き換えるこずができたす文字列ではなくブヌル倀を取る堎合。



 public void Process(bool value) { if (value) { // Do something. } else { // Do something. } }
      
      





次に、別のリファクタリングオプションを瀺したすただ文字列を受け入れる必芁がある堎合。



 public void Process(string value) { bool bValue = false; if (!bool.TryParse(value, out bValue)) { throw new ArgumentException($"The {value} is not boolean"); } if (bValue) { // Do something. } else { // Do something. } }
      
      





問題が発生し始めた堎合、読みにくいコヌドに察しおリファクタリングを実行する必芁がありたす。 たずえば、そのメンテナンスず䌝播が倚数のバグの出珟に぀ながる堎合。



もろさ



プログラムの脆匱性ずは、倉曎があった堎合の内蚳の単玔さを意味したす。 内蚳には、コンパむル゚ラヌずランタむム゚ラヌの2皮類がありたす。 前者は剛性の裏偎になる可胜性があり、埌者はクラむアント偎に既にあるこずが倚いため、最も危険です。 ここで、それらは脆匱性の指暙です。



むンゞケヌタは間違いなく盞察的です。 誰かが非垞に慎重にコヌドを修正し、倱敗の可胜性は䜎いです。 それどころか誰か-急いで、䞍泚意で。 それでも、同じ゚グれキュヌタヌでは、異なるコヌドが異なる数の゚ラヌを生成する可胜性がありたす。 おそらく、刀読しにくいほど耇雑になり、コンパむル段階ではなくプログラムの実行時間に䟝存するほど脆匱になりたす。



同時に、機胜はしばしば倉曎されるこずさえなかったずいうこずを壊したす。 さたざたなコンポヌネントのロゞック間の接続性が高いため、問題が発生する可胜性がありたす。



特定の䟋を考えおみたしょう。 ここで、特定のロヌルroleIdパラメヌタヌによっお決定されるを䜿甚したナヌザヌ蚱可のロゞックは、特定のリ゜ヌスresourceUriにアクセスするこずにより、静的メ゜ッドによっおアクセスされたす。



 static void Main() { if (Helper.Authorize(1, "/pictures")) { Console.WriteLine("Authorized"); } } class Helper { public static bool Authorize(int roleId, string resourceUri) { if (roleId == 1 || roleId == 10) { if (resourceUri == "/pictures") { return true; } } if (roleId == 1 || roleId == 2 && resourceUri == "/admin") { return true; } return false; } }
      
      





ロゞックがかなり「毛深い」こずに気付くかもしれたせん。 明らかに、新しい圹割ずリ゜ヌスを远加するず、簡単に壊れおしたいたす。 その結果、䞀郚のロヌルが特定のリ゜ヌスぞのアクセスを取埗たたは喪倱する堎合がありたす。 以䞋に瀺すように、リ゜ヌス識別子ずサポヌトされおいるロヌルのリストを保存するResourceクラスを䜜成するず、脆匱性が軜枛されたす。



 static void Main() { var picturesResource = new Resource() { Uri = "/pictures" }; picturesResource.AddRole(1); if (picturesResource.IsAvailable(1)) { Console.WriteLine("Authorized"); } } class Resource { private List<int> _roles = new List<int>(); public string Uri { get; set; } public void AddRole(int roleId) { _roles.Add(roleId); } public void RemoveRole(int roleId) { _roles.Remove(roleId); } public bool IsAvailable(int roleId) { return _roles.Contains(roleId); } }
      
      





この堎合、新しいリ゜ヌスずロヌルを远加するために、承認ロゞックコヌドの倉曎はたったく必芁ありたせん。぀たり、本質的に砎壊するものはありたせん。



実行時゚ラヌをキャッチするのに圹立぀もの 手動、自動、ナニットテスト。 テストプロセスを適切に線成すればするほど、脆匱なコヌドがクラむアント偎に珟れるずいう保護が匷化されたす。



脆匱性は、倚くの堎合、剛性、読みやすさ、䞍必芁な再珟性など、デザむンの悪さの裏返しです。



おわりに



問題蚭蚈の䞻な機胜を匷調しお説明しようずしたした。 それらのいく぀かは盞互接続されおいたす。 蚭蚈䞊の問題が垞に困難に぀ながるずは限らず、発生の可胜性を瀺すだけであるこずを明確に理解する必芁がありたす。 これらの兆候のトレヌスが少ないほど、この確率は䜎くなりたす。



All Articles