Методы экономической теории
Метод есть совокупность конкретных приемов, способов и принципов, с помощью которых определяются пути решения поставленных задач.
В системе методов, применяемых в экономической науке, выделяются всеобщие (философские, мировоззренческие), общие научные и частные методы.
В экономической теории применяются два противоположных философских метода – метафизический (рассматривает все явления изолированно, в состоянии неизменяемости) и диалектический. Диалектический метод позволяет более точно отразить действительность, поскольку:
- он исходит из того, что в природе и обществе все явления находятся в постоянном развитии, которое движется от простого к сложному, от низших форм к высшим;
-
он учитывает, что движущая сила развития есть единство и борьба противоположностей, противоречия тех или иных явлений (например, в
Общенаучные методы включают метод научной абстракции, анализ и синтез, индукция и дедукция, единство исторического и логического подходов, качественный и количественный анализ, системный подход.
(1) Одним из важнейших в экономическом анализе является метод научной абстракции. Научная абстракция есть мысленное отвлечение (абстрагирование) от несущественных сторон, свойств явлений (внешней видимой формы) и отыскание главного, наиболее существенного в них. Так улавливается сущность явления. В результате абстрагирования выводятся экономические категории. Они выступают как теоретические выражения реальных сторон экономики (прибыль, цена, товар, деньги , зарплата). В совокупности экономические категории образуют понятийный аппарат. Дальнейшее познание направлено на изучение связи экономических явлений.
(2) Значимым в экономической теории является анализ и синтез. Анализ – расчленение изучаемого явления на составные элементы и детальное изучение каждого из них по отдельности, выяснение его места и роли внутри целого. Синтез – метод, обратный анализу, с его помощью происходит соединение расчлененных и проанализированных элементов в единое целое, раскрывается внутренняя связь между элементами, выясняется их взаимодействие, и в результате воссоздается целостное представление о том или ином явлении.
(3) Выяснению сущности явлений служат
(4) Полезным методом является единство исторического и логического подходов. Его значение состоит в том, что он позволяет не только выяснить происхождение системы и ее элементов, но также обосновать тенденции развития, его этапы. Экономическая теория должна показать явление в развитии, движении, т.е. исторически. В то же время она рассматривает экономические процессы, свободные от случайностей исторического развития, т.е. логически.
(5) Еще один метод – качественный и количественный анализ.
Многие экономические процессы, явления развиваются на основе постепенных количественных изменений. Такие изменения могут осуществляться до определенного уровня, называемого мерой количественных изменений. Когда дальнейшие количественные изменения становятся невозможными в рамках имеющегося качества, то они предполагают изменение качественное.(6) Экономические явления нередко исследуются в рамках системного метода. Это предполагает рассмотрение изучаемого объекта как системы, как совокупности элементов, взаимосвязанных между собой. При этом данная система может являться элементом системы более высокого порядка (уровня). Системный подход предполагает, что экономические явления исследуются по составу и структуре, в определенной субординации, с выделением причинно-следственных связей.
В группе частных приемов
< Предыдущая | Следующая > |
---|
словарные статьи. «Абстракция» (М. А. Розов)
Словарные статьи по системному анализу
Словарь
«Абстракция» (М. А. Розов)
Книги
Лекции
Статьи
Презентации
М. А. Розов
Абстракция (от латинского abstractio — отвлечение):
- Метод научного исследования, основанный на том, что при изучении некоторого явления, процесса не учитываются его несущественные стороны и признаки; это позволяет упрощать картину изучаемого явления и рассматривать его как бы в «чистом виде».
- Продукт познания (понятие, описание, закон, модель, идеальный объект и т. п.), рассмотренный в сопоставлении с конкретной эмпирической действительностью, которая не фиксируется в этом продукте во всем богатстве своих свойств и связей.
- Познавательная деятельность — процесс абстрагирования, — направленная на получение абстракции.
Применение абстракции, как и всякого метода исследования, определяется типом ситуации, с которой сталкивается исследователь, и перечнем процедур, необходимых или допустимых в данной ситуации. Ситуация характеризуется задачей (целевая характеристика метода) и спецификой изучаемого объекта (онтологическая характеристика). С точки зрения онтологии абстракция связана с представлением об относительной независимости явлений и их сторон и с отделением существенных сторон от несущественных. Предписываемые процедуры сводятся, как правило, к перестройке предмета исследования, к эквивалентному, с точки зрения данной ситуации, замещению первоначального предмета другим. Перестройка касается либо изображения изучаемого объекта (отбрасывание несущественных сторон), либо эмпирического материала, либо программы наблюдения и описания (отказ от лишней информации). Например, геометрические свойства электрической цепи не зависят от составляющих элементов ветви, таких, как сопротивление, индуктивность и емкость, поэтому при топологическом изучении цепей все ветви цепи обычно заменяют отрезками, изображая цепь в виде линейного графа.
Целевая характеристика абстракции может быть различной, но всегда связана с указанием на те или иные познавательные задачи и с включением абстракции в более широкий контекст познавательной деятельности. На различении целевых характеристик строится и классификация типов абстракции: изолирующая абстракция соответствует цели вычленения и четкой фиксации исследуемого явления; обобщающая абстракция — цели получения общей картины явления; идеализация — цели замещения реальной, эмпирически данной ситуации идеализированной схемой (типа абсолютно твердого тела в механике) для упрощения изучаемой ситуации и более эффективного использования существующих методов и средств исследования.
Реализация абстракции как метода, т. е. процесс абстрагирования, включает 2 типа операций: выяснение возможности и целесообразности замещения, перестройки предмета исследования и сам акт замещения. Продуктом этого процесса являются соответствующим образом преобразованные представления об объекте, модели, отобранный эмпирический материал и т.
Обоснование абстракции осуществляется как с точки зрения ее правомерности (это достигается установлением несущественности отдельных сторон явления), так и с точки зрения ее целесообразности (которая определяется поставленной задачей). Выдвижение на первый план одного из этих моментов приводит к разным формам обоснования абстракции. Например, в 1-м т. «Капитала» Маркс исходит из предположения, что денежным товаром является только золото, указывая, что он делает это «ради упрощения»; это — целевое обоснование абстракции. Построение идеальных объектов механики обычно обосновывается онтологически, т. е. ссылкой на несущественность тех или иных свойств. Возможность разных способов обоснования абстракции приводит к разным ее пониманиям и определениям в логико-философской литературе: абстракцию определяют то как процесс мысленного отвлечения от несущественных сторон явлений, опираясь на ее процедурные и онтологические аспекты и отвлекаясь от целевых, то как познание явлений со стороны их сущности, опираясь на целевые и онтологические аспекты, но отвлекаясь от процедурных.
Наиболее простой формой абстракции можно считать практическое замещение одного объекта другим. В этой форме абстракция имеет место и у животных (в частности, на ней основано формирование условных рефлексов). У человека такое практическое замещение выражается и закрепляется в слове, и деятельности со знаками. В дальнейшем формируются представления о существенных и несущественных сторонах явлений и об отношениях зависимости. Вместе с тем происходит осознание специфики знаковых образований, их отношения к действительности, к целям и задачам человека. Такое осознание вполне четко видно уже у Аристотеля.
Что же касается метода абстракции, то он возникает на высоких ступенях развития познания. Поэтому следует отличать исторический процесс формирования тех продуктов познавательной деятельности, которые теперь характеризуются как абстракция, от процесса абстрагирования в его современной форме. В первом случае абстрактность самих по себе продуктов познания вовсе не является результатом особой целенаправленной процедуры абстрагирования, она есть суммарный итог человеческой деятельности.
Литература:
- Горский Д. П., Вопросы абстракции и образование понятий, М., 1961;
- Логика научного исследования, М., 1965;
- Розов М. А., Научная абстракция и ее виды, Новосибирск, 1965.
© Виктор Сафронов, 2006–2017
Пользовательское соглашение | RSS | Поддержать проект | Благодарности
Абстрактные типы и методы в Swift
В объектно-ориентированном программировании абстрактный тип обеспечивает базовую реализацию, от которой другие типы могут наследоваться, чтобы получить доступ к некоторым общим, общим функциям. Что отличает абстрактные типы от обычных, так это то, что они никогда не предназначены для использования как есть (на самом деле, некоторые языки программирования даже запрещают прямое создание экземпляров абстрактных типов), поскольку их единственная цель — действовать как общий родитель для группа родственных типов.
Например, предположим, что мы хотели унифицировать способ загрузки определенных типов моделей по сети, предоставив общий API, который мы сможем использовать для разделения задач, облегчения внедрения зависимостей и имитации, а также сохраняйте имена методов согласованными во всем нашем проекте.
Один из основанных на абстрактных типах способов сделать это — использовать базовый класс, который будет действовать как общий унифицированный интерфейс для всех наших типов загрузки модели. Поскольку мы не хотим, чтобы этот класс когда-либо использовался напрямую, мы заставим его вызывать fatalError
, если его базовая реализация вызывается по ошибке:
class Loadable{ функция загрузки (от URL: URL) асинхронные броски -> Модель { fatalError("загрузка(из:) не реализована") } }
Затем каждый подкласс Loadable
переопределит указанный выше метод load
, чтобы обеспечить функциональность загрузки, например:
class UserLoader: Loadable{ переопределить загрузку функции (из URL-адреса: URL) асинхронные броски -> Пользователь { ... } }
Если приведенный выше шаблон выглядит знакомым, то, вероятно, это потому, что это, по сути, тот же тип полиморфизма, для которого мы обычно используем протоколы в Swift. То есть, когда мы хотим определить интерфейс, контракт , которому несколько типов могут соответствовать через различные реализации.
Однако протоколы имеют значительное преимущество перед абстрактными классами, поскольку компилятор обеспечивает правильную реализацию всех их требований, то есть нам больше не нужно полагаться на ошибки времени выполнения (такие как fatalError
) для защиты от ненадлежащего использования, поскольку нет возможности создать протокол отдельно.
Итак, вот как могли бы выглядеть наши прежние типы Loadable
и UserLoader
, если бы мы пошли по протокольно-ориентированному маршруту, а не использовали абстрактный базовый класс:
протокол Loadable { модель ассоциированного типа func load (от url: URL) асинхронные броски -> Модель } класс UserLoader: загружаемый { функция загрузки (из URL: URL) асинхронные броски -> Пользователь { ... } }
Обратите внимание, что теперь мы используем связанный тип, чтобы позволить каждой реализации Loadable
решать, какую именно модель
она хочет загрузить — что дает нам хорошее сочетание полной безопасности типов и большой гибкости.
Итак, в целом протоколы определенно являются предпочтительным способом объявления абстрактных типов в Swift, но это не значит, что они идеальны. На самом деле наша реализация Loadable
на основе протокола в настоящее время имеет два основных недостатка:
- Во-первых, поскольку нам пришлось добавить связанный тип в наш протокол, чтобы сохранить нашу установку универсальной и безопасной для типов, это означает, что
Загружаемый
больше нельзя ссылаться напрямую. - И, во-вторых, поскольку протоколы не могут содержать какую-либо форму хранения, если мы хотим добавить какие-либо сохраненные свойства, которые могли бы использовать все реализации
Loadable
, нам пришлось бы заново объявлять эти свойства в каждом из эти конкретные реализации.
Этот аспект хранения свойств действительно является огромным преимуществом нашей предыдущей установки на основе абстрактных классов. Таким образом, если бы мы вернули Loadable
обратно в класс, то мы смогли бы хранить все объекты, которые потребуются нашим подклассам, прямо в нашем базовом классе, избавляя от необходимости дублировать эти свойства в нескольких типах:
class Загружаемый<Модель> { пусть сеть: Сеть разрешить кеширование: Cacheinit(сеть: Сеть, кэш: Кэш ) { self. networking = сеть self.cache = кеш } функция загрузки (от URL: URL) асинхронные броски -> Модель { fatalError("загрузка(из:) не реализована") } } класс UserLoader: Loadable { переопределить загрузку функции (из URL-адреса: URL) асинхронные броски -> Пользователь { если позволить cachedUser = cache.value(forKey: url) { вернуть cachedUser } let data = попробуйте await networking.data(from: url) ... } }
Итак, здесь мы имеем дело с классическим компромиссным сценарием, где оба подхода (абстрактные классы и протоколы) дают нам разный набор плюсов и минусов. Но что, если бы мы могли объединить их, чтобы получить лучшее из обоих миров?
Если подумать, то единственная реальная проблема с подходом на основе абстрактных классов заключается в том, что fatalError
мы должны были добавить в метод, который должен реализовать каждый подкласс, так что, если бы мы использовали протокол только для этого конкретного метода ? Тогда мы могли бы по-прежнему сохранять наши свойства networking
и cache
в нашем базовом классе — например:
протокол LoadableProtocol { модель ассоциированного типа func load (от url: URL) асинхронные броски -> Модель } класс LoadableBase<Модель> { пусть сеть: Сеть разрешить кеширование: Cacheinit(сеть: Сеть, кэш: Кэш ) { self. networking = сеть self.cache = кеш } }
Основным недостатком этого подхода является то, что все конкретные реализации теперь должны оба подкласса LoadableBase
и объявлять, что они соответствуют нашему новому LoadableProtocol
:
class UserLoader: LoadableBase <Пользователь> , Загружаемый протокол { ... }
Возможно, это не такая уж большая проблема, но, возможно, это делает наш код немного менее элегантным. Хорошая новость заключается в том, что мы действительно можем решить эту проблему, используя универсальный введите псевдоним . Поскольку оператор композиции Swift, и
, поддерживает объединение класса с протоколом, мы можем повторно ввести наш тип Loadable
как комбинацию между LoadableBase
и LoadableProtocol
:
typealias Loadable= LoadableBase< Модель > & LoadableProtocol
Таким образом, конкретные типы (например, UserLoader
) могут просто объявить, что они основаны на Loadable
, и компилятор гарантирует, что все такие типы реализуют наш протокол. 0011 метод загрузки — при этом позволяя этим типам также использовать свойства, объявленные в нашем базовом классе:
class UserLoader: Loadable{ функция загрузки (из URL: URL) асинхронные броски -> Пользователь { если позволить cachedUser = cache.value(forKey: url) { вернуть cachedUser } let data = попробуйте await networking.data(from: url) ... } }
Отлично! Единственным реальным недостатком описанного выше подхода является то, что на Loadable
по-прежнему нельзя ссылаться напрямую, так как под капотом он по-прежнему частично является универсальным протоколом. Однако на самом деле это может не быть проблемой — и если это когда-нибудь случится, мы всегда сможем использовать такие методы, как стирание типов, чтобы обойти такие проблемы.
Еще одно небольшое предостережение с нашей новой настройкой Loadable
на основе псевдонимов типов заключается в том, что такие комбинированные псевдонимы типов не могут быть расширены, что может стать проблемой, если мы хотим предоставить несколько удобных API, которые нам не нужны (или не может ) реализовать непосредственно в нашем классе LoadableBase
.
Однако одним из способов решения этой проблемы было бы объявить все, что необходимо для реализации этих удобных API в нашем протоколе, что затем позволило бы нам расширить этот протокол сам по себе:
протокол LoadableProtocol { модель ассоциированного типа переменная сеть: сеть {получить} var cache: Cache{get} func load (от url: URL) асинхронные броски -> Модель } расширение LoadableProtocol { func loadWithCaching (от url: URL) async throws -> Model { если позволить cachedModel = cache.value (forKey: url) { вернуть cachedModel } пусть модель = попробуйте ожидание загрузки (от: url) cache.insert(модель, forKey: URL) модель возврата } }
Вот несколько разных способов использования абстрактных типов и методов в Swift. Подклассы, возможно, в настоящее время не так популярны, как когда-то (и остаются в других языках программирования), но я по-прежнему думаю, что такие методы здорово иметь в нашем общем наборе инструментов разработки Swift.
Если у вас есть какие-либо вопросы, комментарии или отзывы, не стесняйтесь обращаться через Twitter или по электронной почте.
Спасибо за внимание!
Ваши методы должны быть «Single Level of Abstraction» Long
Большинство разработчиков программного обеспечения понимают, что методы в их коде не должны занимать сотни строк кода (не говоря уже о тысячах). Это один из немногих принципов кодирования, по поводу которого мы, разработчики, пришли к единому мнению. Однако, когда дело доходит до указания длины методов, этот хрупкий консенсус рушится: некоторые говорят, что методы должны помещаться на одном экране; другие ограничивают количество строк определенным числом; разработчики, ориентированные на познание, предполагают, что методы должны быть достаточно короткими, чтобы уместиться в вашей голове; и подходов больше.
Я редко задумываюсь о длине своих методов. Вместо этого я подчиняюсь другому, более важному принципу, который имеет приятный побочный эффект естественного создания более коротких методов. Этот принцип называется принципом единого уровня абстракции (SLAP).
Как следует из названия, принцип единого уровня абстракции гласит, что каждый метод в кодовой базе должен иметь дело с концепциями, относящимися только к одному уровню абстракции. Без смешивания. Звучит просто, но на практике применить этот принцип в реальном коде может быть сложно, потому что он… ну, абстрактный. Поэтому в этом посте я объясню, что означает принцип единого уровня абстракции, и покажу несколько примеров его применения в коде.
Абстракция
Абстракция — это то, что скрывает ненужную информацию. Это определение может показаться совершенно непрактичным, поэтому позвольте мне продемонстрировать, что делают абстракции, на простой аналогии.
При взаимодействии с компьютером вы используете экран, клавиатуру, мышь и другие периферийные устройства. Вам плевать на пластик, стекло, печатные платы, конденсаторы, чипы, вентиляторы и т. д. И уж точно плевать на транзисторы. Все эти низкоуровневые детали, какими бы важными они ни были для работающего компьютера, не имеют значения, когда вы работаете или играете в игры. В этом примере внешние устройства ввода-вывода вашего компьютера, а также его внешнее физическое тело являются абстракциями. Они скрывают от вас большую часть низкоуровневых сведений и предоставляют минимальный объем информации и функций, которые позволяют вам использовать компьютер.
Подумайте вот о чем: если бы нам нужно было понять хотя бы 1% сложности наших компьютеров, прежде чем мы сможем их использовать, большинство людей не смогли бы написать и одной строки текста. Следовательно, абстракции не являются необязательными или второсортными конструкциями. Это обязательные инструменты. Фактически, для большинства пользователей абстракции более высокого уровня (например, веб-сайт) гораздо более реальны, чем любые детали более низкого уровня (например, передача данных).
Исходный код также использует мощь абстракций. Например, класс скрывает большую часть своей сложности от внешнего мира и предоставляет простой набор функций (надеюсь) в форме своего общедоступного API. Итак, класс — это абстракция. Но методы также являются абстракциями, поскольку они также скрывают большую часть своей сложности. А исходный код сам по себе является абстракцией, потому что он позволяет вам использовать такие операторы, как 9.0011 если или вместо
без работы с инструкциями, программными счетчиками, регистрами и т.д.
В общем, везде мы видим абстракции «над» другими абстракциями. Эта прогрессия «уровней абстракции» продолжается и продолжается, и продолжается. Даже транзисторы, которые мы часто рассматриваем как самые фундаментальные компоненты компьютерной инженерии, являются абстракциями более низкого уровня концепций EE и физики твердого тела.
Принцип единого уровня абстракции (SLAP)
Назад к SLAP, который выступает за то, чтобы каждый метод в вашей кодовой базе имел дело только с одним уровнем абстракции.
Это определение может показаться странным, потому что в предыдущем разделе я сказал, что метод сам по себе является абстракцией. Итак, разве метод не имеет дело с тем уровнем абстракции, который связан с тем, что он является методом? Ну в общем то верно. Однако, как и экран, который может одновременно отображать информацию и вводить данные, один объект может представлять разные абстракции, если смотреть с разных точек зрения. Таким образом, метод действительно является абстракцией сам по себе. Это верно со «структурной» точки зрения, если рассматривать его как одну из языковых конструкций. Однако SLAP рассматривает метод как контейнер для кода, который манипулирует другими концептуальными абстракциями, связанными с функциями приложения.
Согласно SLAP, исходный код внутри каждого метода должен относиться к концепциям и механизмам, относящимся только к одному уровню «оперативной сложности» вашего приложения. Например, если метод создает и выполняет HTTP-запросы, он также не должен обрабатывать полученный JSON. Это разные уровни абстракции: один касается связи по HTTP, а другой — определенного формата данных и макета.
В следующих разделах я покажу несколько примеров кода, которые прояснят для вас SLAP.
Рефакторинг для SLAP — пример 1
Рассмотрим этот метод из официального приложения Google IO Scheduler:
override fun toggleFilter(filter: Filter, enabled: Boolean) { если (фильтр !in _filters) { throw IllegalArgumentException("Неподдерживаемый фильтр: $filter") } значение изменено = если (включено) { _selectedFiltersList. add(фильтр) } еще { _selectedFiltersList.remove(фильтр) } если (изменено) { _selectedFilterChipsList = _selectedFiltersList.mapTo(mutableListOf()) { it.asChip(true)} val index = _filterChipsList.indexOfFirst { it.filter == filter } _filterChipsList[index] = filter.asChip(включено) опубликовать (правда) } }
Имя toggleFilter
, так что мы уже можем понять, что он делает. Первый оператор if
проверяет недопустимый аргумент. Второй оператор if
либо добавляет, либо удаляет фильтр, поэтому он связан с фильтрами. Однако реализация этого оператора показывает нам, что фильтры хранятся в каком-то списке. Не критично, но, строго говоря, что-то «переключить» и что-то «добавить» — это не совсем один уровень абстракции. И, наконец, если вторые , если
приводит к изменению, код переходит в третье условие , если
. Этот последний блок кода содержит некоторые низкоуровневые манипуляции, которые не читаются естественно для нетренированного глаза. Поэтому сразу не понять, что такое полный высокоуровневый поток «переключения».
Этот код нарушает SLAP, так что давайте это исправим. Вот с чем я пришел:
override fun toggleFilter(filter: Filter, enabled: Boolean) { если (фильтр !in _filters) { throw IllegalArgumentException("Неподдерживаемый фильтр: $filter") } если (включено) { добавить фильтр (фильтр) } еще { удалить фильтр (фильтр) } } частное развлечение addFilter(filter: Filter) { если (_selectedFiltersList.add (фильтр)) { updateFilterChips (фильтр, правда) } } частная забава removeFilter (фильтр: фильтр) { если (_selectedFiltersList. remove (фильтр)) { updateFilterChips (фильтр, ложь) } } приватное удовольствие updateFilterChips(filter: Filter, isSelected: Boolean) { _selectedFilterChipsList = _selectedFiltersList.mapTo(mutableListOf()) { it.asChip(true)} val index = _filterChipsList.indexOfFirst { it.filter == filter } _filterChipsList[index] = filter.asChip(isSelected) опубликовать (правда) }
Теперь у нас есть явное «переключение», «добавление» и «удаление» функций. Кроме того, читая исходный код, я понял, что существует абстракция под названием «чипы фильтра», которая отличается от «фильтра». Я не знаю, что это значит (и мне все равно), но, согласно SLAP, манипуляции над этой абстракцией также должны быть извлечены в отдельный метод. Так я и сделал.
Я нахожу версию, которая следует за SLAP, более чистой и более читабельной. Теперь, читая этот код, я знаю, когда я перехожу от уровня абстракции «фильтр» к уровню «чипов фильтра», поэтому я могу просто остановиться, если мне не интересно пересекать эту границу. И, конечно же, все эти действия готовы к повторному использованию по желанию, если это потребуется в будущем.
Пожалуйста, обратите внимание, что первоначальная версия не была чрезмерно длинной, поэтому я, вероятно, счел бы нормальным, если бы я оценивал свои методы только по количеству строк кода. Это показывает, насколько мощнее SLAP по сравнению с подсчетом строк.
Для протокола: я тоже не думаю, что рефакторинговый код идеален. Это, конечно, намного лучше оригинала, но все равно довольно грязно. Самая большая оставшаяся проблема — злоупотребление булевыми аргументами метода, но эта проблема выходит за рамки нашего обсуждения здесь.
Рефакторинг для SLAP — пример 2
Второй пример — еще один метод из того же приложения Google IO:
private fun showFeedItems(recyclerView: RecyclerView, list: List?) { если (адаптер == ноль) { val sectionHeaderViewBinder = FeedSectionHeaderViewBinder() val countdownViewBinder = CountdownViewBinder() val momentViewBinder = MomentViewBinder( прослушиватель событий = модель, информация о пользователе = модель. Информация о пользователе, тема = модель.тема ) val sessionViewBinder = FeedSessionsViewBinder(модель) val feedAnnouncementsHeaderViewBinder = ОбъявленияHeaderViewBinder(это, модель) val обьявлениеViewBinder = AnnouncementViewBinder(model.timeZoneId, это) val аннонсемптивиевбиндер = аннонсментсэмптивиевбиндер() val-announcementsLoadingViewBinder = AnnouncementsLoadingViewBinder() val feedSustainabilitySectionViewBinder = FeedSustainabilitySectionViewBinder() val feedSocialChannelsSectionViewBinder = FeedSocialChannelsSectionViewBinder() @Suppress("НЕПРОВЕРЕННЫЙ_КАСТ") val viewBinders = ImmutableMap.builder () .помещать( sectionHeaderViewBinder.modelClass, sectionHeaderViewBinder как FeedItemBinder ) .помещать( обратный отсчетViewBinder. modelClass, countdownViewBinder как FeedItemBinder ) .помещать( моментViewBinder.modelClass, momentViewBinder как FeedItemBinder ) .помещать( сеансыViewBinder.modelClass, sessionViewBinder как FeedItemBinder ) .помещать( каналобъявленийHeaderViewBinder.modelClass, feedAnnouncementsHeaderViewBinder как FeedItemBinder ) .помещать( объявлениеViewBinder.modelClass, объявлениеViewBinder как FeedItemBinder ) .помещать( объявленияEmptyViewBinder.modelClass, объявленияEmptyViewBinder как FeedItemBinder ) .помещать( объявленияLoadingViewBinder.modelClass, объявленияLoadingViewBinder как FeedItemBinder ) . помещать( feedSustainabilitySectionViewBinder.modelClass, feedSustainabilitySectionViewBinder как FeedItemBinder ) .помещать( feedSocialChannelsSectionViewBinder.modelClass, feedSocialChannelsSectionViewBinder как FeedItemBinder ) .строить() адаптер = FeedAdapter (viewBinders) } если (recyclerView.adapter == ноль) { recyclerView.adapter = адаптер } (recyclerView.adapter как FeedAdapter).submitList(list ?: emptyList()) // После отправки списка в адаптер представление переработчика начинает измерение и рисование // поэтому давайте подождем, пока макет будет отрисован, прежде чем сообщать о полной отрисовке. привязка.recyclerView.doOnLayout { // reportFullyDrawn() печатает `I/ActivityTaskManager: Полностью нарисовано {активность} {время}` // в логкэт. Платформа гарантирует, что оператор печатается только один раз за // активности, поэтому нет необходимости добавлять в приложение логику дедупликации. активность?.reportFullyDrawn() } }
Теперь, даже если бы разработчики никогда не слышали о SLAP, этот метод все равно нарушил бы любой разумный предел максимальной длины. В некоторых случаях длинные методы неизбежны (например, длинный блок switch с десятками или даже сотнями случаев), но это не один из них. Это просто грязный код, так что давайте позаботимся об этом.
Большая часть кода этого метода находится внутри начальной проверки if
, которая завершается записью в переменную адаптера . Другими словами, большинство из
showFeedItems
Тело метода не имеет дело ни с лентой, ни с элементами, а с более низкоуровневыми деталями инициализации адаптера. Затем у нас есть еще одно условие if, которое очень озадачивает, потому что оно кажется ненужным и, опять же, имеет дело с адаптером. Затем у нас есть одна строка кода, которая (наконец) связана с абстракцией элементов фида и использует список, переданный в метод, за которым следует какой-то сильно закомментированный и малопонятный код более низкого уровня. Всего внутри этого метода всего одна строчка кода, напрямую связанная с уровнем абстракции, указанным в его названии. Грязный.
Вот что у меня получилось после рефакторинга по SLAP:
private fun showFeedItems(list: List?) { обеспечитьFeedUiInitialized() bindFeedItemsToAdapter (список ?: пустой список ()) отчетFullyDrawnAfterFeedItemsDrawn() } приватное развлечение если (адаптер == ноль) { val viewBinders = инициализироватьViewBinders() адаптер = FeedAdapter (viewBinders) } если (recyclerView. adapter == ноль) { recyclerView.adapter = адаптер } } частная забава initializeViewBinders () { val sectionHeaderViewBinder = FeedSectionHeaderViewBinder() val countdownViewBinder = CountdownViewBinder() val momentViewBinder = MomentViewBinder( прослушиватель событий = модель, информация о пользователе = модель.Информация о пользователе, тема = модель.тема ) val sessionViewBinder = FeedSessionsViewBinder(модель) val feedAnnouncementsHeaderViewBinder = ОбъявленияHeaderViewBinder(это, модель) val обьявлениеViewBinder = AnnouncementViewBinder(model.timeZoneId, это) val аннонсемптивиевбиндер = аннонсментсэмптивиевбиндер() val-announcementsLoadingViewBinder = AnnouncementsLoadingViewBinder() val feedSustainabilitySectionViewBinder = FeedSustainabilitySectionViewBinder() val feedSocialChannelsSectionViewBinder = FeedSocialChannelsSectionViewBinder() @Suppress("НЕПРОВЕРЕННЫЙ_КАСТ") val viewBinders = ImmutableMap. builder () .помещать( sectionHeaderViewBinder.modelClass, sectionHeaderViewBinder как FeedItemBinder ) .помещать( обратный отсчетViewBinder.modelClass, countdownViewBinder как FeedItemBinder ) .помещать( моментViewBinder.modelClass, momentViewBinder как FeedItemBinder ) .помещать( сеансыViewBinder.modelClass, sessionViewBinder как FeedItemBinder ) .помещать( каналобъявленийHeaderViewBinder.modelClass, feedAnnouncementsHeaderViewBinder как FeedItemBinder ) .помещать( объявлениеViewBinder.modelClass, объявлениеViewBinder как FeedItemBinder ) .помещать( объявленияEmptyViewBinder.modelClass, объявленияEmptyViewBinder как FeedItemBinder ) . помещать( объявленияLoadingViewBinder.modelClass, объявленияLoadingViewBinder как FeedItemBinder ) .помещать( feedSustainabilitySectionViewBinder.modelClass, feedSustainabilitySectionViewBinder как FeedItemBinder ) .помещать( feedSocialChannelsSectionViewBinder.modelClass, feedSocialChannelsSectionViewBinder как FeedItemBinder ) .строить() вернуть viewBinders } частное развлечение bindFeedItemsToAdapter(feedItems: List ) { (binding.recyclerView.adapter as FeedAdapter).submitList(feedItems) } частный отчет о развлеченияхFullyDrawnAfterFeedItemsDrawn() { // После отправки списка в адаптер представление переработчика начинает измерение и рисование // поэтому давайте подождем, пока макет будет отрисован, прежде чем сообщать о полной отрисовке. привязка.recyclerView.doOnLayout { // reportFullyDrawn() печатает `I/ActivityTaskManager: Полностью нарисовано {активность} {время}` // в логкэт. Платформа гарантирует, что оператор печатается только один раз за // активности, поэтому нет необходимости добавлять в приложение логику дедупликации. активность?.reportFullyDrawn() } }
После этого рефакторинга становится очень ясно, что происходит, когда мы хотим показать элементы ленты. Большая часть кода теперь находится внутри метода, в котором четко указано, что он имеет дело с абстракцией «привязки представлений», поэтому вы можете пропустить его при чтении кода, если вас не интересует такой уровень детализации. Обратите внимание, что этот метод initializeViewBinders
по-прежнему относительно длинный, но это совершенно нормально, поскольку он имеет дело только с одним уровнем абстракции. Разделение этого метода на самом деле не имело бы смысла, потому что это привело бы к фрагментации композиции 9.0011 Сопоставить структуру данных без усиления. Следовательно, следование SLAP в этом случае автоматически создает оптимальный код.
Как и прежде, для протокола скажу, что код после рефакторинга все еще не чист. Опытные разработчики Android заметят, что то, как они работают с адаптером, бессмысленно, и в коде есть ненужные типы, допускающие значение NULL, но ничего из этого не связано с SLAP.
Заключение
Принцип единого уровня абстракции является исключительно мощной эвристикой для написания чистого кода. Это приводит к более простому, более читаемому и более пригодному для повторного использования коду, а также освобождает вас от необходимости думать о длине ваших методов.
Преимущества SLAP становятся еще более очевидными при рефакторинге унаследованного кода, поскольку SLAP помогает вам выполнять множество небольших поэтапных рефакторингов, которые вы можете легко реализовать. Эти небольшие шаги со временем приведут к сложному проценту и могут превратиться в серьезные архитектурные улучшения.
Поскольку «абстракции» в вашем коде точно не определены, SLAP не является точной наукой.