Является ли использование многих статических методов плохим?

Я склонен объявлять статическими все методы в classе, когда этот class не требует отслеживания внутренних состояний. Например, если мне нужно преобразовать A в B и не полагаться на какое-то внутреннее состояние C, которое может меняться, я создаю статическое преобразование. Если есть внутреннее состояние C, которое я хочу настроить, тогда я добавляю конструктор для установки C и не использую статическое преобразование.

Я читал различные рекомендации (в том числе в StackOverflow) НЕ для чрезмерного использования статических методов, но я до сих пор не понимаю, что это неправильно с правилом выше.

Это разумный подход или нет?

Существует два типа общих статических методов:

  • «Безопасный» статический метод всегда будет давать одинаковый вывод для одних и тех же входных данных. Он не модифицирует глобальные переменные и не вызывает никаких «опасных» статических методов любого classа. По сути, вы используете ограниченное функциональное программирование – не бойтесь их, они в порядке.
  • «Небезопасный» статический метод мутирует глобальное состояние, или прокси-серверы к глобальному объекту, или какое-то другое не проверяемое поведение. Это возврат к процедурному программированию и должен быть реорганизован, если это вообще возможно.

Существует несколько распространенных применений «небезопасной» статики – например, в шаблоне Singleton, но имейте в виду, что, несмотря на любые красивые имена, которые вы им называете, вы просто изменяете глобальные переменные. Перед использованием небезопасной статики тщательно подумайте.

Объект без какого-либо внутреннего состояния является подозрительной вещью.

Обычно объекты инкапсулируют состояние и поведение. Объект, который инкапсулирует только поведение, является нечетным. Иногда это пример Легкого или Мухи .

В других случаях это процедурный дизайн на языке объектов.

Это действительно только продолжение замечательного ответа Джона Милликина.


Хотя может быть безопасным делать статичные методы (которые являются довольно многими функциями) статическими, иногда это может привести к мутации, которую трудно модифицировать. У вас есть статический метод как таковой:

public class StaticClassVersionOne { public static void doSomeFunkyThing(int arg); } 

Который вы называете:

 StaticClassVersionOne.doSomeFunkyThing(42); 

Что хорошо, хорошо и очень удобно, пока вы не столкнетесь с ситуацией, когда вам нужно изменить поведение статического метода и обнаружите, что вы тесно связаны с StaticClassVersionOne . Возможно, вы могли бы изменить код, и все будет хорошо, но если бы другие пользователи были зависимы от старого поведения, их нужно было бы учитывать в теле метода. В некоторых случаях тело этого метода может стать довольно уродливым или недостижимым, если оно пытается сбалансировать все эти поведения. Если вы разделите методы, вам может потребоваться изменить код в нескольких местах, чтобы учесть его, или сделать вызовы для новых classов.

Но подумайте, создали ли вы интерфейс для предоставления метода и предоставили его вызывающим абонентам, теперь, когда поведение должно измениться, можно создать новый class для реализации интерфейса, который является более чистым, более простым в тестировании и более удобным для обслуживания, и это вместо этого предоставляется вызывающим. В этом случае вызывающие classы не нужно изменять или даже перекомпилировать, а изменения локализуются.

Это может быть или не может быть вероятной ситуацией, но я думаю, что это стоит рассмотреть.

Другой вариант – добавить их как нестатические методы для исходного объекта:

т.е. изменение:

 public class BarUtil { public static Foo transform(Bar toFoo) { ... } } 

в

 public class Bar { ... public Foo transform() { ...} } 

однако во многих ситуациях это невозможно (например, генерация обычного кода кода из XSD / WSDL / etc), или это сделает class очень длинным, а методы преобразования часто могут быть реальной болью для сложных объектов, и вы просто хотите их в своем отдельном classе. Так что да, у меня есть статические методы в служебных classах.

Статические classы прекрасны, если они используются в правильных местах.

А именно: методы, которые являются «листовыми» методами (они не изменяют состояние, они просто каким-то образом преобразуют вход). Хорошими примерами этого являются такие вещи, как Path.Combine. Такие вещи полезны и делают синтаксис терминов.

Проблемы, которые у меня есть со статикой, многочисленны:

Во-первых, если у вас есть статические classы, зависимости скрыты. Рассмотрим следующее:

 public static class ResourceLoader { public static void Init(string _rootPath) { ... etc. } public static void GetResource(string _resourceName) { ... etc. } public static void Quit() { ... etc. } } public static class TextureManager { private static Dictionary m_textures; public static Init(IEnumerable _formats) { m_textures = new Dictionary(); foreach(var graphicsFormat in _formats) { // do something to create loading classes for all // supported formats or some other contrived example! } } public static Texture GetTexture(string _path) { if(m_textures.ContainsKey(_path)) return m_textures[_path]; // How do we know that ResourceLoader is valid at this point? var texture = ResourceLoader.LoadResource(_path); m_textures.Add(_path, texture); return texture; } public static Quit() { ... cleanup code } } 

Глядя на TextureManager, вы не можете определить, какие шаги инициализации необходимо выполнить, посмотрев на конструктор. Вы должны вникать в class, чтобы найти его зависимости и инициализировать вещи в правильном порядке. В этом случае перед запуском необходимо инициализировать ResourceLoader. Теперь увеличьте этот кошмар зависимости, и вы, вероятно, догадаетесь, что произойдет. Представьте, что вы пытаетесь поддерживать код, где нет явного порядка инициализации. Сравните это с инъекцией зависимостей с экземплярами – в этом случае код даже не будет скомпилирован, если зависимости не будут выполнены!

Кроме того, если вы используете статику, которая меняет состояние, это похоже на карточный домик. Вы никогда не знаете, кто имеет доступ к чему, и дизайн имеет тенденцию напоминать монстра спагетти.

Наконец, и так же важно, использование статики связывает программу с конкретной реализацией. Статический код является антитезой проектирования для проверки. Испытательный код, который пронизан статикой, является кошмаром. Статический вызов никогда не может быть заменен на тестовый двойной (если вы не используете тестовые фреймворки, специально предназначенные для издевательства со статическими типами), поэтому статическая система вызывает все, что использует ее, как мгновенный интеграционный тест.

Короче говоря, статика хороша для некоторых вещей, а для небольших инструментов или кода выброса я бы не стал препятствовать их использованию. Однако помимо этого, они являются кровавым кошмаром для ремонтопригодности, хорошего дизайна и простоты тестирования.

Вот хорошая статья о проблемах: http://gamearchitect.net/2008/09/13/an-anatomy-of-despair-managers-and-contexts/

Причина, по которой вас предупреждают статические методы, заключается в том, что использование их лишает одно из преимуществ объектов. Объекты предназначены для инкапсуляции данных. Это предотвращает появление неожиданных побочных эффектов, что позволяет избежать ошибок. Статические методы не содержат инкапсулированных данных * и поэтому не получают этого преимущества.

Тем не менее, если вы не используете внутренние данные, они могут использоваться и немного быстрее выполнять. Убедитесь, что вы не касаетесь глобальных данных.

  • Некоторые языки также имеют переменные уровня classа, которые позволяли бы инкапсулировать данные и статические методы.

Это кажется разумным подходом. Причина, по которой вы не хотите использовать слишком много статических classов / методов, заключается в том, что вы в конечном итоге уходите от объектно-ориентированного программирования и больше в сферу структурированного программирования.

В вашем случае, когда вы просто трансформируете A в B, скажите, что все, что мы делаем, – это преобразование текста, чтобы перейти от

 "hello" =>(transform)=> "Hello!" 

Тогда статический метод имел бы смысл.

Однако, если вы часто вызываете эти статические методы на объекте, и он имеет тенденцию быть уникальным для многих вызовов (например, способ, которым вы его используете, зависит от ввода), или он является частью неотъемлемого поведения объекта, он будет быть разумным сделать его частью объекта и поддерживать его состояние. Один из способов сделать это – реализовать его как интерфейс.

 class Interface{ method toHtml(){ return transformed string (eg "Hello!") } method toConsole(){ return transformed string (eg "printf Hello!") } } class Object implements Interface { mystring = "hello" //the implementations of the interface would yield the necessary //functionality, and it is reusable across the board since it //is an interface so... you can make it specific to the object method toHtml() method toConsole() } 

Редактирование: одним из хороших примеров использования статических методов являются html-вспомогательные методы в Asp.Net MVC или Ruby. Они создают элементы html, которые не привязаны к поведению объекта и поэтому статичны.

Редактировать 2: Изменено функциональное программирование на структурированное программирование (по какой-то причине я запутался), реквизит Торстен для указания этого.

Недавно я реорганизовал приложение для удаления / изменения некоторых classов, которые изначально были реализованы как статические classы. Со временем эти classы приобрели так много, и люди просто пометили новые функции как статические, так как никогда не было экземпляра, плавающего вокруг.

Итак, мой ответ заключается в том, что статические classы не являются по своей сути плохими, но может быть проще начать создавать экземпляры сейчас, а затем придется реорганизовать позже.

Я бы подумал, что это дизайнерский запах. Если вы обнаружите, что используете в основном статические методы, у вас, вероятно, нет очень хорошего дизайна OO. Это не обязательно плохо, но, как и со всеми запахами, это заставит меня остановиться и переоценить. Он намекает, что вы, возможно, сможете улучшить дизайн OO, или, может быть, вам стоит пойти в другом направлении и полностью избегать OO для этой проблемы.

До тех пор, пока не вступает в действие внутреннее состояние, это прекрасно. Обратите внимание, что обычно статические методы должны быть streamобезопасными, поэтому, если вы используете вспомогательные структуры данных, используйте их в streamобезопасном режиме.

Раньше я ездил туда и обратно между classом с кучей статических методов и синглом. Оба решения проблемы, но синглтон можно гораздо легче заменить более чем одним. (Программисты всегда кажутся настолько уверенными, что будет только что-то, и я обнаружил, что недостаточно времени, чтобы полностью отказаться от статических методов, за исключением некоторых очень ограниченных случаев).

Во всяком случае, синглтон дает вам возможность позже передать что-то на фабрику, чтобы получить другой экземпляр и который изменяет поведение всей вашей программы без рефакторинга. Изменение глобального classа статических методов на что-то с разными «резервными» данными или несколько другим поведением (дочерним classом) является главной болью в прикладе.

И статические методы не имеют такого же преимущества.

Так что да, они плохие.

Если это полезный метод, приятно сделать его статическим. Гуава и Apache Commons основываются на этом принципе.

Мое мнение об этом чисто прагматично. Если это ваш код приложения, статические методы, как правило, не самые лучшие. Статические методы имеют серьезные ограничения по единичному тестированию – их нелегко высмеять: вы не можете вводить издеваемую статическую функциональность в какой-то другой тест. Вы также обычно не можете вводить функциональность в статический метод.

Таким образом, в моей логике приложения у меня обычно есть небольшие статические вызовы, подобные утилитам. Т.е.

 static cutNotNull(String s, int length){ return s == null ? null : s.substring(0, length); } 

одним из преимуществ является то, что я не тестирую такие методы 🙂

Ну, конечно, нет серебряной пули. Статические classы подходят для небольших утилит / помощников. Но использование статических методов для программирования бизнес-логики, безусловно, является злым. Рассмотрим следующий код

  public class BusinessService { public Guid CreateItem(Item newItem, Guid userID, Guid ownerID) { var newItemId = itemsRepository.Create(createItem, userID, ownerID); **var searchItem = ItemsProcessor.SplitItem(newItem);** searchRepository.Add(searchItem); return newItemId; } } 

Вы видите статический вызов метода ItemsProcessor.SplitItem(newItem); Это пахнет причиной

  • У вас нет явной зависимости, и если вы не вникнете в код, вы можете упустить связь между вашим classом и контейнером статического метода
  • Вы не можете протестировать BusinessService изолируя его от ItemsProcessor (большинство инструментов тестирования не издеваются над статическими classами), и это делает модульное тестирование невозможным. Нет тестов единиц == низкое качество

Если вы знаете, что вам никогда не понадобится использовать внутреннее состояние C, все в порядке. Однако, если это когда-либо изменится в будущем, вам нужно будет сделать метод нестационарным. Если это не статично, вы можете просто игнорировать внутреннее состояние, если оно вам не нужно.

Статические методы обычно являются плохим выбором даже для кода без атак. Вместо этого создайте одноэлементный class с этими методами, который создается один раз и вводится в classы, желающие использовать методы. Такие classы легче издеваться и тестироваться. Они гораздо ориентированы на объекты. При необходимости их можно связать с прокси-сервером. Статика делает OO намного сложнее, и я не вижу причин использовать их почти во всех случаях. Не 100%, а почти все.

  • Когда это делать?
  • Случай использования в реальном мире побитовых операторов
  • Что такое экзистенциальный тип?
  • Interesting Posts

    Как перебирать ключи и значения с помощью ng-repeat в AngularJS?

    Как скрыть кнопку программно?

    applicationWillEnterForeground против applicationDidBecomeActive, applicationWillResignActive vs. applicationDidEnterBackground

    Проблема с кодировкой Java 8 UTF-8 (ошибка java?)

    Извлечь числовую часть строк смешанных чисел и символов в R

    API Карт Google v2: как сделать маркеры доступными для кликов?

    Файловая система, используемая для внешнего жесткого диска для использования с компьютерами Mac, Linux и Windows

    dynamic_cast и static_cast в C ++

    Как провести тест производительности моего интернет-соединения?

    Оператор обновления с внутренним соединением в Oracle

    У нас есть какие-либо общие функции, чтобы проверить, полностью ли загружена страница в Selenium

    Код для загрузки видео с Youtube на Java, Android

    Кто предоставляет интернет-услуги интернет-провайдерам (ISP)?

    Вывод Unicode на консоль Использование C ++ в Windows

    Как создать контрольные массивы в VB .NET

    Давайте будем гением компьютера.