Я пишу классы, которые «должны использоваться особым образом» (я думаю, все классы должны ...).
Например, я создаю fooManager
класс, который требует вызова, скажем, Initialize(string,string)
. И, чтобы продвинуть пример немного дальше, класс был бы бесполезен, если бы мы не слушали его ThisHappened
действие.
Я хочу сказать, что класс, который я пишу, требует вызовов методов. Но он будет хорошо скомпилирован, если вы не вызовете эти методы, и в результате вы получите пустой новый FooManager. В какой-то момент он либо не будет работать, либо может зависнуть, в зависимости от класса и того, что он делает. Программист, который реализует мой класс, очевидно, заглянет в него и поймет: «О, я не вызывал Initialize!», И это было бы хорошо.
Но мне это не нравится. В идеале я хотел бы, чтобы код НЕ компилировался, если метод не вызывался; Я предполагаю, что это просто невозможно. Или что-то, что было бы сразу видно и ясно.
Меня беспокоит текущий подход, который я здесь использую, а именно:
Добавьте приватное логическое значение в класс и везде проверяйте, инициализирован ли класс; если нет, я сгенерирую исключение, говорящее «класс не был инициализирован, вы уверены, что звоните .Initialize(string,string)
?».
Я в порядке с таким подходом, но он приводит к большому количеству кода, который компилируется и, в конце концов, не нужен конечному пользователю.
Кроме того, иногда даже больше кода, когда есть больше методов, чем просто Initiliaze
вызов. Я стараюсь держать в своих занятиях не слишком много открытых методов / действий, но это не решает проблему, а просто делает ее разумной.
То, что я ищу здесь:
- Мой подход правильный?
- Есть ли лучший?
- Что вы, ребята, делаете / советуете?
- Я пытаюсь решить не проблему? Мне сказали коллеги, что программист должен проверить класс, прежде чем пытаться его использовать. Я с уважением не согласен, но это другое дело, я верю.
Проще говоря, я пытаюсь найти способ никогда не забывать реализовывать вызовы, когда этот класс используется повторно позже или кем-то еще.
РАЗЪЯСНЕНИЯ:
Чтобы уточнить многие вопросы здесь:
Я определенно говорю не только об части инициализации класса, а скорее о целой жизни. Не позволяйте коллегам дважды вызывать метод, следя за тем, чтобы они вызывали X перед Y и т. Д. Все, что в конечном итоге было бы обязательным и в документации, но мне бы хотелось, чтобы в коде он был как можно более простым и небольшим. Мне очень понравилась идея Asserts, хотя я совершенно уверен, что мне нужно смешать некоторые другие идеи, поскольку Asserts не всегда будут возможны.
Я использую язык C #! Как я не упомянул это ?! Я работаю в среде Xamarin и создаю мобильные приложения, в которых обычно используется от 6 до 9 проектов, включая проекты PCL, iOS, Android и Windows. Я был разработчиком около полутора лет (школа и работа вместе), поэтому мои иногда смешные заявления \ вопросы. Все это, вероятно, здесь неактуально, но слишком много информации не всегда плохо.
Я не всегда могу поместить все, что является обязательным, в конструктор из-за ограничений платформы и использования Dependency Injection, когда параметры, отличные от Interfaces, не обсуждаются. Или, может быть, моих знаний недостаточно, что весьма возможно. Большую часть времени это не проблема инициализации, но больше
как я могу убедиться, что он зарегистрирован на это событие?
как я могу убедиться, что он не забыл «остановить процесс в какой-то момент»
Здесь я помню класс извлечения рекламы. До тех пор, пока видимость объявления видима, класс будет получать новое объявление каждую минуту. Этот класс нуждается в представлении, когда он создан, где он может отображать объявление, которое может явно входить в параметр. Но как только представление исчезнет, необходимо вызвать StopFetching (). В противном случае класс продолжит получать рекламу для просмотра, которого даже нет, и это плохо.
Кроме того, в этом классе есть события, которые необходимо прослушивать, например, AdClicked. Все работает нормально, если не слушать, но мы теряем отслеживание аналитики там, если отводы не зарегистрированы. Реклама по-прежнему работает, поэтому пользователь и разработчик не увидят разницы, а аналитика просто будет иметь неверные данные. Этого следует избегать, но я не уверен, как разработчик может знать, что он должен зарегистрироваться на событие Дао. Хотя это упрощенный пример, но идея есть: «убедитесь, что он использует публичное действие, которое доступно» и в нужное время, конечно!
initialize
быть сделан поздно после создания объекта? Будет ли ctor слишком «рискованным» в том смысле, что он может генерировать исключения и разрывать цепочку создания?new
если объект не готов к использованию. Полагаться на метод инициализации непременно придется преследовать вас, и его следует избегать, если в этом нет крайней необходимости, по крайней мере, таков мой опыт.Ответы:
В таких случаях лучше всего использовать систему типов вашего языка, чтобы помочь вам с правильной инициализацией. Как мы можем предотвратить
FooManager
от быть использованы без инициализации? Путь предотвращенияFooManager
от того создаются без необходимой информации для правильной инициализации его. В частности, вся инициализация является обязанностью конструктора. Вы никогда не должны позволять своему конструктору создавать объект в недопустимом состоянии.Но вызывающие абоненты должны создать a,
FooManager
прежде чем они смогут его инициализировать, например, потому чтоFooManager
он передается как зависимость.Не создавайте,
FooManager
если у вас его нет. Вместо этого вы можете передать объект, который позволяет вам получить полностью построенный объектFooManager
, но только с информацией об инициализации. (Говоря о функциональном программировании, я предлагаю вам использовать частичное приложение для конструктора.) Например:Проблема в том, что вы должны предоставлять информацию об инициализации каждый раз, когда вы получаете доступ к
FooManager
.Если это необходимо на вашем языке, вы можете заключить
getFooManager()
операцию в класс, подобный фабрике, или класс, похожий на конструктор.Я действительно хочу делать проверки во время выполнения, что
initialize()
метод был вызван, а не использовать решение на уровне системы типов.Можно найти компромисс. Мы создаем класс-обертку,
MaybeInitializedFooManager
который имеетget()
метод, который возвращаетFooManager
, но выдает, еслиFooManager
не был полностью инициализирован. Это работает, только если инициализация выполняется через обертку или если естьFooManager#isInitialized()
метод.Я не хочу менять API своего класса.
В этом случае вы захотите избежать
if (!initialized) throw;
условных выражений в каждом методе. К счастью, есть простой способ решить эту проблему.Объект, который вы предоставляете пользователям, - это просто пустая оболочка, которая делегирует все вызовы объекту реализации. По умолчанию объект реализации выдает ошибку для каждого метода, который не был инициализирован. Однако
initialize()
метод заменяет объект реализации полностью сконструированным объектом.Это извлекает основное поведение класса в
MainImpl
.источник
FooManager
и inUninitialisedImpl
лучше, чем повторениеif (!initialized) throw;
.FooManager
есть много методов, это может быть проще, чем потенциально забыть некоторыеif (!initialized)
проверки. Но в этом случае вы, вероятно, предпочтете разбить класс.Самый эффективный и полезный способ предотвратить "неправильное" использование объекта клиентами - сделать его невозможным.
Самое простое решение - слить
Initialize
с конструктором. Таким образом, объект никогда не будет доступен для клиента в неинициализированном состоянии, поэтому ошибка невозможна. Если вы не можете выполнить инициализацию в самом конструкторе, вы можете создать фабричный метод. Например, если ваш класс требует регистрации определенных событий, вы можете потребовать, чтобы прослушиватель событий был параметром в сигнатуре конструктора или фабричного метода.Если вам нужно иметь доступ к неинициализированному объекту до инициализации, вы можете реализовать два состояния как отдельные классы, поэтому вы начнете с
UnitializedFooManager
экземпляра, у которого естьInitialize(...)
метод, который возвращаетInitializedFooManager
. Методы, которые можно вызывать только в инициализированном состоянии, существуют только вInitializedFooManager
. Этот подход может быть распространен на несколько состояний, если вам нужно.По сравнению с исключениями времени выполнения, это больше работы для представления состояний как отдельных классов, но это также дает вам гарантии времени компиляции, что вы не вызываете методы, которые являются недопустимыми для состояния объекта, и это более четко документирует переходы состояний в коде.
Но в целом, идеальным решением является разработка ваших классов и методов без ограничений, таких как требование вызова методов в определенное время и в определенном порядке. Это не всегда возможно, но во многих случаях этого можно избежать, используя соответствующий шаблон.
Если у вас сложная временная связь (несколько методов должны вызываться в определенном порядке), одним из решений может быть использование инверсии управления , поэтому вы создаете класс, который вызывает методы в соответствующем порядке, но использует шаблоны методов или события позволить клиенту выполнять пользовательские операции на соответствующих этапах потока. Таким образом, ответственность за выполнение операций в правильном порядке переходит от клиента к самому классу.
Простой пример: у вас есть
File
-объект, который позволяет вам читать из файла. Тем не менее, клиент должен вызватьOpen
до того, какReadLine
метод может быть вызван, и должен помнить, чтобы всегда вызыватьClose
(даже если возникает исключение), после чегоReadLine
метод больше не должен вызываться. Это временная связь. Этого можно избежать, используя единственный метод, который принимает в качестве аргумента обратный вызов или делегат. Метод управляет открытием файла, вызовом обратного вызова и последующим закрытием файла. Он может передавать отличный интерфейс сRead
методом к обратному вызову. Таким образом, клиент не может забыть вызывать методы в правильном порядке.Интерфейс с временной связью:
Без временной связи:
Более тяжелым решением будет определение
File
в качестве абстрактного класса, который требует от вас реализации (защищенного) метода, который будет выполняться в открытом файле. Это называется шаблоном метода шаблона.Преимущество то же самое: клиент не должен помнить, чтобы вызывать методы в определенном порядке. Просто невозможно вызвать методы в неправильном порядке.
источник
UnitializedFooManager
) не должны совместно использовать состояние с instances (InitializedFooManager
), как наInitialize(...)
самом деле имеетInstantiate(...)
смысл. В противном случае этот пример приводит к новым проблемам, если один и тот же шаблон используется разработчиком дважды. И это невозможно предотвратить / проверить статически, если язык явно не поддерживаетmove
семантику, чтобы надежно использовать шаблон.Обычно я просто проверяю инициализацию и выкидываю (скажем),
IllegalStateException
если вы пытаетесь использовать его, пока он не инициализирован.Однако, если вы хотите быть безопасным во время компиляции (и это похвально и предпочтительно), почему бы не рассматривать инициализацию как фабричный метод, возвращающий построенный и инициализированный объект, например
и поэтому вы контролируете создание объектов и жизненный цикл, и ваши клиенты получают в
Component
результате вашей инициализации. Это фактически ленивый экземпляр.источник
IllegalStateException
.IllegalStateException
илиInvalidOperationException
неожиданно, пользователь никогда не поймет, что он сделал не так. Эти исключения не должны стать препятствием для исправления ошибки дизайна.Я собираюсь немного оторваться от других ответов и не согласиться: невозможно ответить на этот вопрос, не зная, на каком языке вы работаете. Является ли это целесообразным планом и правильным «предупреждением», которое нужно дать Ваши пользователи полностью зависят от механизмов, предоставляемых вашим языком, и от соглашений, которым обычно придерживаются другие разработчики этого языка.
Если у вас есть
FooManager
Haskell, было бы преступно разрешать вашим пользователям создавать тот, который не способен управлятьFoo
s, потому что система типов делает это очень простым, и это те соглашения, которые ожидают разработчики Haskell.С другой стороны, если вы пишете C, ваши коллеги были бы полностью в своих правах отобрать вас и отстрелить вас за определение отдельных типов
struct FooManager
иstruct UninitializedFooManager
типов, которые поддерживают различные операции, потому что это привело бы к излишне сложному коду с очень небольшой выгодой. ,Если вы пишете на Python, в языке нет механизма, позволяющего вам это делать.
Вы, вероятно, не пишете на Haskell, Python или C, но они являются иллюстративными примерами с точки зрения того, сколько / как мало работы система типов ожидает и может выполнить.
Следуйте разумным ожиданиям разработчика на вашем языке и не поддавайтесь искушению чрезмерно спроектировать решение, которое не имеет естественной идиоматической реализации (если ошибку не так легко совершить и так сложно уловить, что стоит пойти на крайние меры длины, чтобы сделать это невозможно). Если у вас недостаточно опыта работы с языком, чтобы судить о том, что является разумным, следуйте советам того, кто знает это лучше вас.
источник
Поскольку вы, кажется, не хотите отправлять проверку кода клиенту (но, кажется, это хорошо для программиста), вы можете использовать функции assert , если они доступны на вашем языке программирования.
Таким образом, у вас есть проверки в среде разработки (и любые тесты, которые разработчик назвал бы, БЕЗ предсказуемо провалится), но вы не отправите код заказчику, поскольку утверждения (по крайней мере, в Java) компилируются только выборочно.
Итак, класс Java, использующий это, будет выглядеть так:
Утверждения являются отличным инструментом для проверки состояния вашей программы во время выполнения, которое вам требуется, но на самом деле не ожидайте, что кто-либо когда-либо поступит неправильно в реальной среде.
Кроме того, они довольно тонкие и не занимают больших частей синтаксиса if () throw ..., их не нужно перехватывать и т. Д.
источник
Глядя на эту проблему более широко, чем текущие ответы, которые в основном сосредоточены на инициализации. Рассмотрим объект, который будет иметь два метода,
a()
иb()
. Требование заключается в том, чтоa()
всегда вызывается раньшеb()
. Вы можете создать проверку во время компиляции, чтобы это происходило, возвращая новый объектa()
и перемещаясьb()
к новому объекту, а не к исходному. Пример реализации:Теперь невозможно вызвать b () без предварительного вызова a (), так как оно реализовано в интерфейсе, который скрыт до вызова a (). Следует признать, что это довольно много усилий, так что я бы не обычно использую этот подход, но бывают ситуации , когда это может быть полезным (особенно , если ваш класс будет повторно использоваться во многих ситуациях программистов , которые не могут быть знакомы с его реализацией или в коде, где надежность имеет решающее значение). Также обратите внимание, что это обобщение шаблона построителя, как это предлагается во многих существующих ответах. Он работает почти так же, единственное реальное различие заключается в том, где хранятся данные (в исходном объекте, а не в возвращенном) и когда он предназначен для использования (в любое время и только во время инициализации).
источник
Когда я реализую базовый класс, который требует дополнительной информации об инициализации или ссылок на другие объекты, прежде чем он станет полезным, я стремлюсь сделать этот базовый класс абстрактным, а затем определить несколько абстрактных методов в этом классе, которые используются в потоке базового класса (например,
abstract Collection<Information> get_extra_information(args);
иabstract Collection<OtherObjects> get_other_objects(args);
который по договору наследования должен быть реализован конкретным классом, заставляя пользователя этого базового класса предоставлять все вещи, требуемые базовым классом.Таким образом, когда я реализую базовый класс, я сразу и ясно знаю, что мне нужно написать, чтобы базовый класс вел себя правильно, поскольку мне просто нужно реализовать абстрактные методы и все.
РЕДАКТИРОВАТЬ: чтобы уточнить, это почти то же самое, что и предоставление аргументов конструктору базового класса, но реализации абстрактного метода позволяют реализации обрабатывать аргументы, передаваемые вызовам абстрактного метода. Или даже в случае отсутствия аргументов, это может быть полезно, если возвращаемое значение абстрактного метода зависит от состояния, которое вы можете определить в теле метода, что невозможно при передаче переменной в качестве аргумента конструктор Конечно, вы все равно можете передать аргумент, который будет иметь динамическое поведение, основанное на том же принципе, если вы предпочитаете использовать композицию вместо наследования.
источник
Ответ: да, нет, а иногда. :-)
Некоторые из описанных вами проблем легко решаются, по крайней мере, во многих случаях.
Проверка во время компиляции, безусловно, предпочтительнее проверки во время выполнения, что предпочтительнее, чем вообще никакой проверки.
RE время выполнения:
Концептуально просто заставить функции вызываться в определенном порядке и т. Д. С помощью проверок во время выполнения. Просто вставьте флаги, которые говорят, какая функция была запущена, и пусть каждая функция начинается с чего-то вроде «если не pre-condition-1 или pre-condition-2, то выведите исключение». Если проверки становятся сложными, вы можете поместить их в приватную функцию.
Вы можете сделать некоторые вещи автоматически. Чтобы взять простой пример, программисты часто говорят о «ленивом населении» объекта. Когда экземпляр создается, для флага is-заполнено значение false или для некоторой ссылки на ключевой объект - значение null. Затем, когда вызывается функция, требующая соответствующих данных, она проверяет флаг. Если это false, он заполняет данные и устанавливает флаг true. Если это правда, это просто продолжает предполагать, что данные есть. Вы можете сделать то же самое с другими предпосылками. Установите флаг в конструкторе или в качестве значения по умолчанию. Затем, когда вы достигнете точки, в которой должна была быть вызвана некоторая функция предварительного условия, если она еще не была вызвана, вызовите ее. Конечно, это работает, только если у вас есть данные, чтобы вызвать его в этот момент, но во многих случаях вы можете включить любые необходимые данные в параметры функции "
Время компиляции RE:
Как уже говорили другие, если вам нужно инициализировать перед использованием объекта, это помещает инициализацию в конструктор. Это довольно типичный совет для ООП. Если это вообще возможно, вы должны заставить конструктор создать допустимый, пригодный для использования экземпляр объекта.
Я вижу обсуждение того, что делать, если вам нужно передать ссылки на объект до его инициализации. Я не могу придумать реальный пример этого из головы, но я полагаю, что это могло произойти. Решения были предложены, но решения, которые я видел здесь, и все, что я могу придумать, являются грязными и усложняют код. В какой-то момент вы должны спросить: стоит ли делать некрасивый, трудный для понимания код, чтобы мы могли проводить проверку во время компиляции, а не во время выполнения? Или я создаю много работы для себя и других ради цели, которая хороша, но не обязательна?
Если две функции всегда должны выполняться вместе, простое решение - сделать их одной функцией. Например, если каждый раз, когда вы добавляете рекламу на страницу, вы также должны добавить для нее обработчик метрик, а затем поместить код для добавления обработчика метрик в функцию «добавить рекламу».
Некоторые вещи было бы почти невозможно проверить во время компиляции. Как требование, чтобы определенная функция не вызывалась более одного раза. (Я написал много подобных функций. Я только недавно написал функцию, которая ищет файлы в волшебном каталоге, обрабатывает все, что находит, а затем удаляет их. Конечно, после удаления файла перезапустить невозможно. И т. Д.) Я не знаю ни одного языка, в котором есть функция, позволяющая предотвратить повторный вызов функции на одном и том же экземпляре во время компиляции. Я не знаю, как компилятор мог окончательно выяснить, что это происходит. Все, что вы можете сделать, это поставить проверки во время выполнения. Эта проверка времени выполнения очевидна, не так ли? msgstr "если уже было здесь = истина, тогда выдается исключение, иначе установлено уже было здесь = истина".
RE невозможно обеспечить
Для класса нередко требуется некоторая очистка, когда вы закончите: закрытие файлов или соединений, освобождение памяти, запись окончательных результатов в БД и т. Д. Нет простого способа заставить это произойти во время компиляции. или время выполнения. Я думаю, что большинство языков ООП имеют своего рода функцию для функции «финализатора», которая вызывается, когда экземпляр собирается мусором, но я думаю, что большинство также говорят, что они не могут гарантировать, что это когда-либо будет выполнено. Языки ООП часто включают функцию «dispose» с какими-то условиями для установки области действия при использовании экземпляра, предложения «using» или «with», и когда программа выходит из этой области действия, выполняется dispose. Но для этого программист должен использовать соответствующий спецификатор области видимости. Это не заставляет программиста делать это правильно,
В случаях, когда невозможно заставить программиста правильно использовать класс, я стараюсь сделать так, чтобы программа взорвалась, если он сделал это неправильно, вместо того, чтобы давать неверные результаты. Простой пример: я видел много программ, которые инициализируют кучу данных фиктивными значениями, так что пользователь никогда не получит исключение нулевого указателя, даже если ему не удастся вызвать функции для правильного заполнения данных. И мне всегда интересно, почему. Вы изо всех сил стараетесь найти ошибки. Если, когда программисту не удалось вызвать функцию «загрузить данные», а затем беззаботно попытался использовать данные, он получил исключение нулевого указателя, исключение быстро показало бы ему, в чем проблема. Но если вы скрываете это, делая данные пустыми или равными нулю в таких случаях, программа может работать до конца, но давать неточные результаты. В некоторых случаях программист может даже не осознавать, что возникла проблема. Если он заметит, ему, возможно, придется пройти длинный путь, чтобы выяснить, в чем дело. Лучше потерпеть неудачу рано, чем попытаться смело продолжать.
В общем, конечно, всегда хорошо, когда вы можете сделать некорректное использование объекта просто невозможным. Хорошо, если функции структурированы таким образом, что у программиста просто нет возможности делать недопустимые вызовы, или если недопустимые вызовы вызывают ошибки во время компиляции.
Но есть также момент, когда вы должны сказать: программист должен прочитать документацию по функции.
источник
Да, ваши инстинкты точны. Много лет назад я писал статьи в журналах (вроде как это место, но на мертвом дереве и выпускал их раз в месяц), обсуждая отладку , отладку и отладку .
Если вы можете заставить компилятор отлавливать неправильное использование, это лучший способ. Какие языковые функции доступны, зависит от языка, и вы не указали. В любом случае, конкретные методы будут подробно описаны для отдельных вопросов на этом сайте.
Но наличие теста для определения использования во время компиляции, а не во время выполнения - это на самом деле тест того же типа: вам все равно нужно знать, чтобы сначала вызывать нужные функции и правильно их использовать.
Проектирование компонента, чтобы избежать того, что даже проблема в первую очередь, намного тоньше, и мне нравится, что Buffer подобен примеру Element . Часть 4 (опубликованная двадцать лет назад! Вау!) Содержит пример с обсуждением, очень похожим на ваш случай: этот класс должен использоваться осторожно, так как buffer () может не вызываться после того, как вы начнете использовать read (). Скорее, он должен использоваться только один раз, сразу после строительства. Если бы класс управлял буфером автоматически и незаметно для вызывающей стороны, это концептуально позволило бы избежать проблемы.
Вы спрашиваете, если тесты могут быть выполнены во время выполнения для обеспечения надлежащего использования, вы должны это сделать?
Я бы сказал, да . Это сэкономит вашей команде большую часть работы по отладке когда-нибудь, когда будет поддерживаться код и нарушается конкретное использование. Тестирование может быть настроено как формальный набор ограничений и инвариантов, которые могут быть протестированы. Это не означает, что накладные расходы на дополнительное пространство для отслеживания состояния и работы по проверке всегда остаются за каждым вызовом. Он находится в классе, поэтому его можно вызывать, а код документирует фактические ограничения и предположения.
Возможно, проверка выполняется только в отладочной сборке.
Возможно, проверка сложна (например, вспомним о heapcheck), но она присутствует и может выполняться время от времени, или добавляются вызовы тут и там, когда над кодом работает, и возникает какая-то проблема, или для проведения специального тестирования для убедитесь, что в программе модульного или интеграционного тестирования , связанной с одним и тем же классом, такой проблемы нет .
Вы можете решить, что накладные расходы в конце концов тривиальны. Если класс выполняет файловый ввод-вывод, и дополнительный байт состояния и проверка на это ничего не значат. Обычные классы iostream проверяют, находится ли поток в плохом состоянии.
Ваши инстинкты хороши. Так держать!
источник
Принцип сокрытия информации (инкапсуляции) заключается в том, что сущности вне класса не должны знать больше, чем требуется для правильного использования этого класса.
В вашем случае кажется, что вы пытаетесь заставить объект класса работать должным образом, не рассказывая достаточно о внешних пользователях о классе. Вы скрыли больше информации, чем должны были.
Мудрые слова:
* В любом случае вам придется перепроектировать класс и / или конструктор и / или методы.
* Не спроектировав должным образом и лишив класс правильной информации, вы рискуете, что ваш класс сломается не в одном, а потенциально в нескольких местах.
* Если вы плохо написали исключения и сообщения об ошибках, ваш класс будет выдавать еще больше о себе.
* Наконец, если посторонний должен знать, что он должен инициализировать a, b и c, а затем вызвать d (), e (), f (int), то у вас есть утечка в вашей абстракции.
источник
Поскольку вы указали, что используете C #, жизнеспособным решением вашей ситуации является использование анализаторов кода Roslyn . Это позволит вам сразу же обнаруживать нарушения и даже предлагать исправления кода .
Одним из способов его реализации было бы оформление связанных во времени методов с помощью атрибута, который определяет порядок, в котором методы должны быть вызваны 1 . Когда анализатор находит эти атрибуты в классе, он проверяет, что методы вызываются по порядку. Это сделает ваш класс похожим на следующее:
1: я никогда не писал Roslyn Code Analyzer, так что это может быть не самая лучшая реализация. Идея использовать Roslyn Code Analyzer для проверки правильности использования вашего API, однако, на 100% обоснована.
источник
Я решил эту проблему раньше, используя приватный конструктор и статический
MakeFooManager()
метод в классе. Пример:Так как конструктор реализован, никто не сможет создать экземпляр
FooManager
без прохожденияMakeFooManager()
.источник
Есть несколько подходов:
Сделайте класс легким в использовании правильным и трудным в неправильном. Некоторые другие ответы сосредоточены исключительно на этом вопросе, поэтому я просто упомяну RAII и шаблон сборки .
Будьте внимательны к тому, как использовать комментарии. Если класс говорит сам за себя, не пишите комментарии. * Таким образом, люди с большей вероятностью будут читать ваши комментарии, когда они действительно нужны классу. И если у вас есть один из этих редких случаев, когда класс трудно использовать правильно и требует комментариев, включите примеры.
Используйте утверждения в ваших отладочных сборках.
Бросайте исключения, если использование класса недопустимо.
* Это комментарии, которые вы не должны писать:
источник