<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/">
  <channel>
    <language>ru</language>
    <title>Основные принципы программирования</title>
    <description/>
    <link>https://tproger.ru/tag/programming-concepts</link>
    <atom:link href="https://tproger.ru/tag/programming-concepts/feed" rel="self" type="application/rss+xml"/>
    <lastBuildDate>Thu, 01 Oct 2026 14:14:53 GMT</lastBuildDate>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>12</sy:updateFrequency>
    <image>
      <title>Основные принципы программирования</title>
      <link>https://tproger.ru</link>
      <url>https://tproger.ru/apple-touch-icon.png</url>
    </image>
    <item>
      <title>Хорошо ли вы знаете принципы SOLID — тест</title>
      <link>https://tproger.ru/quiz/horowo-li-vy-znaete-principy-solid---test</link>
      <comments>https://tproger.ru/quiz/horowo-li-vy-znaete-principy-solid---test?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Дух айтишной эмо школы]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/quiz/horowo-li-vy-znaete-principy-solid---test</guid>
      <description><![CDATA[<p>SOLID — это ключевые принципы в объектно-ориентированном программировании. Проверьте свои знания SOLID, ответив на 10 вопросов. </p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/quiz/horowo-li-vy-znaete-principy-solid---test">Хорошо ли вы знаете принципы SOLID — тест</a>»</p>]]></description>
      <category><![CDATA[Объектно-ориентированное программирование]]></category>
      <category><![CDATA[Основные принципы программирования]]></category>
      <category><![CDATA[Викторины]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 30 Jan 2024 10:03:21 GMT</pubDate>
      <content:encoded><![CDATA[<p>SOLID — это ключевые принципы в объектно-ориентированном программировании. Они помогают разработчикам создавать более устойчивый, гибкий и адаптируемый к изменениям системы код.</p><p>В этом тесте вы сможете проверить свои знания SOLID, ответив на ряд вопросов. На каждый вопрос можно дать только один верный ответ.</p><p>Удачи!</p>]]></content:encoded>
    </item>
    <item>
      <title>Основные принципы ООП: полиморфизм в программировании</title>
      <link>https://tproger.ru/articles/osnovnye-principy-oop-polimorfizm-v-programmirovanii</link>
      <comments>https://tproger.ru/articles/osnovnye-principy-oop-polimorfizm-v-programmirovanii?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Марина Александровна]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/osnovnye-principy-oop-polimorfizm-v-programmirovanii</guid>
      <description><![CDATA[<p>Полиморфизм — один из основных принципов ООП: что это такое, в каких случаях используется, а также наглядный пример полиморфизма в ООП.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/osnovnye-principy-oop-polimorfizm-v-programmirovanii">Основные принципы ООП: полиморфизм в программировании</a>»</p>]]></description>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Объектно-ориентированное программирование]]></category>
      <category><![CDATA[Основные принципы программирования]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 24 Aug 2023 12:26:20 GMT</pubDate>
      <content:encoded><![CDATA[<p>Одним из ключевых принципов ООП является полиморфизм — концепция, позволяющая создавать более гибкие, расширяемые и понимаемые программы. В этой статье мы рассмотрим суть полиморфизма, его типы и приведем примеры кода для лучшего понимания данного принципа ООП.</p><h2>Для чего нужен полиморфизм в программировании?</h2><p>Полиморфизм в контексте ООП означает, что разные объекты могут реагировать на один и тот же запрос, проявляя разное поведение в зависимости от своего типа. Это позволяет сократить дублирование кода, улучшить читаемость и облегчить расширение программы.</p><h2>Преимущества принципа полиморфизма</h2><ol><li>Гибкость и расширяемость. Полиморфизм позволяет добавлять новые типы объектов и операций без изменения существующего кода. Новые классы, реализующие общий интерфейс, могут быть легко интегрированы в существующую систему.</li><li>Упрощение кода. Полиморфизм способствует уменьшению дублирования кода. Общий интерфейс или абстрактный базовый класс позволяют описать общее поведение, и каждый конкретный класс реализует только свою специфичную логику.</li><li>Читаемость кода. Полиморфизм делает код более интуитивно понимаемым, так как работа с различными объектами происходит через общий интерфейс. Это упрощает восприятие кода другими разработчиками и способствует поддержке программы.</li><li>Расширение функциональности. Добавление новых функций или операций для существующих классов становится проще. Достаточно реализовать необходимые методы в новых классах, которые наследуют общий интерфейс.</li><li>Повторное использование кода. Полиморфизм позволяет использовать одни и те же методы для разных типов данных. Это устраняет необходимость создания аналогичных функций для разных классов.</li><li>Улучшение тестирования. Тестирование становится более удобным, так как можно создать общие тестовые сценарии для всех классов, реализующих один интерфейс. Это способствует повышению качества и надежности программы.</li><li><a href="https://tproger.ru/articles/osnovnye-principy-oop-abstrakciya-v-programmirovanii">Абстракция</a> и <a href="https://tproger.ru/articles/osnovnye-principy-oop-inkapsulyaciya-v-programmirovanii/">инкапсуляция</a>. Полиморфизм позволяет абстрагироваться от конкретных реализаций и сосредоточиться на общем поведении объектов. Также он способствует инкапсуляции, разделяя интерфейс от деталей реализации.</li><li>Облегчение командной разработки. Когда разработчики работают над разными частями программы, полиморфизм позволяет им взаимодействовать через общие интерфейсы без необходимости глубокого понимания внутренней реализации друг друга.</li></ol><h2>Виды полиморфизма в объектно-ориентированном программировании</h2><p>Разные виды полиморфизма в объектно-ориентированном программировании обеспечивают гибкость и расширяемость кода. Они позволяют обращаться с разными типами данных единообразно, что делает программы более понятными и удобными для разработки и обслуживания программного кода.</p><h3>1. Полиморфизм подтипов (наследования)</h3><p>Этот вид полиморфизма основан на <a href="https://tproger.ru/articles/osnovnye-principy-oop-nasledovanie-v-programmirovanii">наследовании</a> и позволяет объектам дочерних классов использоваться как объекты родительского класса. Это делает код более гибким и облегчает добавление новых типов.</p><h3>2. Параметрический полиморфизм (обобщённое программирование)</h3><p>Параметрический полиморфизм позволяет создавать обобщенные функции и классы, которые могут работать с разными типами данных без знания их конкретной природы.</p><h3>3. Полиморфизм в интерфейсах</h3><p>Интерфейсный полиморфизм позволяет объектам разных классов реализовывать общий интерфейс и предоставлять схожее поведение без явного наследования.</p><p>Полиморфизм — это суть объектно-ориентированного программирования, позволяющая создавать гибкие и расширяемые программы. Благодаря различным видам полиморфизма, разработчики могут писать более чистый, читаемый и эффективный код. Овладение этим принципом существенно обогатит навыки любого программиста и сделает его программы более элегантными и функциональными.</p>]]></content:encoded>
    </item>
    <item>
      <title>Основные принципы ООП: наследование в программировании</title>
      <link>https://tproger.ru/articles/osnovnye-principy-oop-nasledovanie-v-programmirovanii</link>
      <comments>https://tproger.ru/articles/osnovnye-principy-oop-nasledovanie-v-programmirovanii?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Марина Александровна]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/osnovnye-principy-oop-nasledovanie-v-programmirovanii</guid>
      <description><![CDATA[<p>О принципе наследования в ООП простыми словами. Объясняем механизм наследования ООП и преимущества метода на примере Java-кода.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/osnovnye-principy-oop-nasledovanie-v-programmirovanii">Основные принципы ООП: наследование в программировании</a>»</p>]]></description>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Объектно-ориентированное программирование]]></category>
      <category><![CDATA[Основные принципы программирования]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 27 Jul 2023 06:34:13 GMT</pubDate>
      <content:encoded><![CDATA[<p>Принцип программирования наследование является одним из ключевых понятий в ООП. Он позволяет создавать иерархии классов, где один класс (подкласс) наследует свойства и методы другого класса (суперкласса). Это позволяет сокращать дублирование кода, упрощать структуру программы и создавать более логичные иерархии объектов.</p><h2>Преимущества принципа наследования</h2><p>Принцип наследования является одним из фундаментальных понятий объектно-ориентированного программирования и предоставляет несколько преимуществ, которые делают его важным и полезным инструментом при проектировании программных систем:</p><ol><li>Повторное использование кода. Наследование позволяет создавать иерархии классов, где общая функциональность реализуется в родительском классе, и все подклассы автоматически наследуют этот код. Это способствует повторному использованию кода, что уменьшает дублирование и облегчает его поддержку.</li><li>Расширяемость. Принцип наследования позволяет создавать новые классы, расширяющие функциональность существующих классов. Подклассы могут добавлять новые свойства и методы, а также переопределять поведение унаследованных методов. Это делает код более гибким и позволяет легко вносить изменения.</li><li>Упрощение кода. Использование наследования позволяет разбивать большие и сложные классы на более мелкие и управляемые части. Каждый подкласс специализируется на определенном аспекте функциональности, что упрощает понимание и поддержку кода.</li><li>Полиморфизм. Наследование поддерживает концепцию полиморфизма, которая позволяет обращаться к объектам подклассов через ссылки на родительские классы. Это облегчает обработку групп объектов с различными типами, что упрощает написание общего и универсального кода.</li><li><a href="https://tproger.ru/articles/osnovnye-principy-oop-abstrakciya-v-programmirovanii">Абстракция</a>. Наследование позволяет выделить общие характеристики объектов и создать абстрактные классы, которые определяют интерфейс для группы связанных классов. Абстрактные классы предоставляют общую сущность без необходимости определения всех деталей реализации.</li><li>Структурирование кода. Наследование помогает упорядочить классы в логические иерархии, что улучшает структуру программы. Каждый класс наследует функциональность от одного или нескольких родительских классов, что улучшает организацию кода и делает его более понятным и легко поддерживаемым.</li></ol><p>В целом, принцип наследования позволяет создавать более гибкие, модульные и расширяемые программы, что упрощает разработку и сопровождение сложных проектов. Он способствует повторному использованию кода и помогает соблюдать принципы DRY (Don’t Repeat Yourself) и <a href="https://tproger.ru/articles/principy-solid-python/">SOLID</a>, что в свою очередь способствует созданию качественного и эффективного кода.</p><h2>Пример наследования в ООП</h2><p>Рассмотрим пример кода на Java:</p><p>В этом примере у нас есть родительский класс Animal, который содержит общие свойства и методы для всех животных. Затем есть два подкласса Dog и Cat, которые наследуют свойства и методы от класса Animal. Каждый подкласс также имеет свои собственные уникальные свойства и методы. Обратите внимание на использование ключевого слова extends при объявлении подклассов.</p><p>Принцип наследования позволяет нам использовать общие характеристики и функциональность из родительского класса и при этом иметь возможность расширять или переопределять их в подклассах для создания более специфичных типов объектов.</p>]]></content:encoded>
    </item>
    <item>
      <title>Основные принципы ООП: инкапсуляция в программировании</title>
      <link>https://tproger.ru/articles/osnovnye-principy-oop-inkapsulyaciya-v-programmirovanii</link>
      <comments>https://tproger.ru/articles/osnovnye-principy-oop-inkapsulyaciya-v-programmirovanii?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Марина Александровна]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/osnovnye-principy-oop-inkapsulyaciya-v-programmirovanii</guid>
      <description><![CDATA[<p>Основные принципы ООП включают в себя инкапсуляцию. Рассмотрим главные преимущества принципа и пример инкапсуляции данных.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/osnovnye-principy-oop-inkapsulyaciya-v-programmirovanii">Основные принципы ООП: инкапсуляция в программировании</a>»</p>]]></description>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Объектно-ориентированное программирование]]></category>
      <category><![CDATA[Основные принципы программирования]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sun, 23 Jul 2023 11:22:02 GMT</pubDate>
      <content:encoded><![CDATA[<p>Принцип инкапсуляции является одним из основных принципов объектно-ориентированного программирования и подразумевает скрытие внутренней реализации объекта от внешнего мира. Это означает, что данные и методы, которые оперируют с этими данными, объединяются в единое целое, называемое классом.</p><p>Внешний код может взаимодействовать с объектом только через определенные интерфейсы, предоставленные классом, не имея прямого доступа к его внутренним данным.</p><h2>Преимущества принципа инкапсуляции</h2><p>Инкапсуляция данных имеет множество практических применений в разработке программного обеспечения. Вот несколько примеров использования <a href="https://ru.wikipedia.org/wiki/%D0%98%D0%BD%D0%BA%D0%B0%D0%BF%D1%81%D1%83%D0%BB%D1%8F%D1%86%D0%B8%D1%8F_(%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)">инкапсуляции</a> на практике:</p><ol><li>Безопасность данных. Инкапсуляция позволяет защитить данные объекта от некорректного доступа и изменения извне. Класс может предоставлять только определенные методы (геттеры и сеттеры) для доступа к данным, которые проверяют правильность операций и обеспечивают безопасность данных.</li><li>Сокрытие реализации. Когда класс инкапсулирует свою реализацию, то изменения внутри класса не отражаются на внешнем коде. Это позволяет менять реализацию объекта, не нарушая функциональность клиентского кода, что облегчает поддержку и эволюцию программы.</li><li>Упрощение интерфейса. Инкапсуляция позволяет предоставить простой и понятный интерфейс для работы с объектами. Клиентский код взаимодействует только с публичными методами класса, не требуя знания деталей его внутренней реализации.</li><li>Модульность. Инкапсуляция помогает создавать модульные системы, где каждый класс представляет собой отдельный модуль со своими данными и методами. Модули могут взаимодействовать друг с другом через публичные интерфейсы, что способствует повышению читаемости и понимаемости кода.</li><li>Принцип единственной ответственности. Инкапсуляция способствует соблюдению принципа единственной ответственности (Single Responsibility Principle). Класс, инкапсулирующий определенные данные и операции с ними, должен отвечать только за эти данные и их обработку.</li><li>Контроль доступа. Инкапсуляция ООП позволяет устанавливать уровни доступа к данным и методам класса. Таким образом, некоторые данные и функциональность могут быть скрыты от других классов или пакетов, что способствует защите и контролю кода.</li></ol><p>Применение процесса инкапсуляции в практике программирования помогает создавать более структурированный, безопасный и расширяемый код. Она способствует лучшему управлению сложностью программы и облегчает сотрудничество между разработчиками при разработке больших проектов.</p><h2>Пример инкапсуляции в ООП</h2><p>Рассмотрим инкапсуляцию на простом примере:</p><p>В приведенном примере класс BankAccount инкапсулирует данные о банковском счете (accountNumber и balance) и предоставляет интерфейс для работы с ними. Поля accountNumber и balance объявлены как private, что делает их доступными только внутри класса.</p><p>Для доступа к данным счета извне класса используются публичные методы (геттеры и сеттеры). В данном случае, методы getAccountNumber() и getBalance() позволяют получить номер счета и баланс соответственно. А методы deposit() и withdraw() предоставляют возможность внести или снять деньги со счета.</p><p>Таким образом, внешний код может использовать объект BankAccount, зная только публичные методы, а не внутренние детали реализации класса. Это позволяет безопасно изменять внутреннюю реализацию класса BankAccount, не затрагивая код, который с ним взаимодействует.</p><p>Читайте также о <a href="https://tproger.ru/articles/osnovnye-principy-oop-abstrakciya-v-programmirovanii/">принципе абстракции в ООП</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Принципы SOLID на примерах Python</title>
      <link>https://tproger.ru/articles/principy-solid-python</link>
      <comments>https://tproger.ru/articles/principy-solid-python?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Марина Александровна]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/principy-solid-python</guid>
      <description><![CDATA[<p>Принципы SOLID на примерах Python-кода, с подробным объяснением преимуществ и возможных недостатков каждого принципа.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/principy-solid-python">Принципы SOLID на примерах Python</a>»</p>]]></description>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Рефакторинг]]></category>
      <category><![CDATA[Основные принципы программирования]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sun, 09 Jul 2023 11:02:27 GMT</pubDate>
      <content:encoded><![CDATA[<p>Вероятно, вы не раз слышали о так называемых SOLID принципах. Но что на самом деле означает каждый из принципов SOLID и как правильно применять их на практике? Вы найдёте ответы в данной статье.</p><h2>Что такое SOLID принципы?</h2><p>Современная разработка требует передовых навыков и глубоких знаний сложных принципов программирования. Одной из наиболее важных структур, используемых сегодня в разработке программного обеспечения, являются принципы SOLID — они же пять основных принципов объектно-ориентированного программирования.</p><p>Понимание SOLID является обязательным для всех разработчиков, и в этой статье мы объясним их простым языком.</p><h2>Расшифровка SOLID</h2><p>Аббревиатура SOLID включает в себя название пяти принципов «хорошего дизайна», когда речь идет о разработке программного обеспечения. Акроним SOLID означает следующие принципы:</p><ol><li>Принцип единственной ответственности (Single Responsibility Principle – SRP): Каждый класс должен иметь только одну причину для изменения. Это означает, что класс должен быть ответственным только за одну конкретную функцию или задачу. Этот принцип помогает сделать классы более связанными, легко понятными и поддерживаемыми.</li><li>Принцип открытости/закрытости (Open/Closed Principle – OCP): Программные сущности, такие как классы, модули и функции, должны быть открыты для расширения, но закрыты для модификации. Вместо изменения существующего кода, следует добавлять новый код для внесения изменений. Это позволяет создавать более стабильные и гибкие системы.</li><li>Принцип подстановки Лисков (Liskov Substitution Principle – LSP): Объекты в программе должны быть заменяемыми экземплярами их базовых типов, не нарушая корректность программы. Это означает, что код, который работает с базовым типом, должен работать и с любым его подтипом, не вызывая ошибок или неожиданного поведения. Этот принцип обеспечивает согласованность в использовании наследования и полиморфизма.</li><li>Принцип разделения интерфейса (Interface Segregation Principle – ISP): Клиенты не должны зависеть от интерфейсов, которые они не используют. Вместо создания общих интерфейсов следует создавать специфические интерфейсы, предназначенные для конкретных клиентов. Это позволяет избежать излишней связности между компонентами системы и улучшить модульность.</li><li>Принцип инверсии зависимостей (Dependency Inversion Principle – DIP): Классы должны зависеть от абстракций, а не от конкретных реализаций. Высокоуровневые модули не должны зависеть от низкоуровневых модулей. Оба типа модулей должны зависеть от абстракций. Этот принцип помогает уменьшить связанность между компонентами системы и повысить их переиспользуемость.</li></ol><p>Все эти пять принципов SOLID способствуют созданию гибкого и легко поддерживаемого кода, а также улучшают модульность, переиспользуемость и расширяемость системы.</p><p>Акроним SOLID принципов был сформулирован Робертом С. Мартином, также известным как «Дядя Боб», одним из самых влиятельных разработчиков программного обеспечения в области разработки программного обеспечения.</p><p>Хотя многим эти принципы могут показаться сложными, мы постараемся объяснить их простым и понятным способом.</p><h2>Принцип единой ответственности</h2><p>Мы начнем с принципа единой ответственности, который гласит, что каждый класс или компонент кода должен иметь только одну ответственность. Это также означает, что у него должна быть только одна причина для изменения. Чтобы проиллюстрировать принцип, давайте рассмотрим пример.</p><p>Предположим, у нас есть файл с именем «автомобиль» с кодом, отвечающим за эксплуатацию, управление, контроль и мониторинг автомобиля. В соответствии с принципом единой ответственности каждая из этих задач должна быть разделена на отдельный файл или компонент. Также стоит отметить, что SRP применяется не только к классам, но и к функциям и методам.</p><h3>SOLID примеры на Python — принцип единой ответственности</h3><p>Концепция принципа единой ответственности (SRP) заключается в том, что класс должен иметь только одну причину для изменения. Давайте рассмотрим пример с классом User, который нарушает этот принцип:</p><p>В этом примере класс User имеет несколько ответственностей. Он отвечает за сохранение пользователя в базе данных, отправку электронной почты и генерацию отчета. Как результат, класс становится сильно связанным и трудным для понимания и поддержки.</p><p>Давайте применим принцип единой ответственности, разделив класс User на несколько отдельных классов с единой ответственностью:</p><p>Теперь каждый класс имеет только одну ответственность. Класс User отвечает только за данные пользователя и сохранение их в базе данных. Класс EmailSender занимается отправкой электронной почты, а класс ReportGenerator отвечает за генерацию отчета пользователя.</p><p>Это пример применения принципа единой ответственности, который помогает разделить функциональность на более мелкие и специализированные классы, упрощает понимание кода и делает его более гибким для изменений.</p><h3>Визуальное представление SRP</h3><p>Визуализация SRP, как одного из SOLID принципов, может быть представлена в виде диаграммы классов, где каждый класс имеет только одну ответственность. Каждый класс должен быть ответственным только за выполнение одной конкретной функции или задачи. Если класс имеет несколько ответственностей, это может указывать на нарушение SRP:</p><p>В этой визуализации каждый класс представлен прямоугольником, и у каждого класса есть только свои собственные методы, относящиеся только к его ответственности.</p><p>Важно отметить, что это просто концептуальное представление, и сама диаграмма может быть намного более сложной в реальном приложении. Однако основная идея заключается в том, чтобы иметь ясное представление о том, что каждый класс имеет только одну ответственность, и у классов нет перекрестных зависимостей между своими ответственностями.</p><h3>Резюмируя</h3><p>Принцип единой ответственности требует, чтобы классы имели только одну ответственность. Это может показаться простым, но это одна из самых важных концепций, которые необходимо понимать при разработке высококачественного программного обеспечения. Идея в том, что каждая часть кода должна отвечать только за одну задачу. Например, у класса должны быть только связанные методы и переменные для его конкретной задачи, и ничего больше.</p><h2>Принцип открытого-закрытого</h2><p>Теперь рассмотрим принцип открытого-закрытого. Он предполагает, что программные компоненты должны быть открыты для расширения и закрыты для любых модификаций. Другими словами, когда создаются новые функции, код следует расширять, не затрагивая его существующей структуры. Для этого разработчики могут использовать комбинацию наследования и полиморфизма. Важным преимуществом соблюдения принципа «открыто-закрыто» является снижение риска непредвиденных ошибок при внесении изменений.</p><h3>SOLID примеры на Python — принцип открытого-закрытого</h3><p>Принцип открытого-закрытого (Open/Closed Principle – OCP) заключается в том, что программные сущности, такие как классы, модули и функции, должны быть открыты для расширения, но закрыты для модификации. Давайте рассмотрим пример с классами Shape и AreaCalculator, где будем применять принцип OCP:</p><p>В этом примере у нас есть абстрактный класс Shape, который определяет метод calculate_area(). Затем у нас есть два класса-наследника Rectangle и Circle, которые реализуют этот метод для расчета площади прямоугольника и круга соответственно.</p><p>Класс AreaCalculator отвечает за вычисление общей площади для набора фигур. Он использует SOLID принцип OCP, поскольку он открыт для расширения новыми типами фигур, но закрыт для модификации своей основной логики. Если мы хотим добавить новый тип фигуры, например, треугольник, мы можем создать новый класс Triangle, реализующий метод calculate_area(), и передать его в AreaCalculator.calculate_total_area() без изменения самого AreaCalculator:</p><p>Таким образом, принцип открытого-закрытого позволяет нам добавлять новые типы фигур, расширяя функциональность, без изменения существующего кода в AreaCalculator. Это делает код более гибким и устойчивым к изменениям.</p><h3>Резюмируя</h3><p>Принцип открытого-закрытого гласит, что классы должны быть открыты для расширения, но закрыты для модификации. Расширение класса не должно требовать модификации существующего кода. Это гарантирует, что код может быть изменен и расширен с минимальным нарушением остальной части кодовой базы.</p><h2>Принцип подстановки Барбары Лисков</h2><p>Принцип подстановки Лисков — еще одна важная концепция. Он предполагает, что объекты подклассов должны быть полностью взаимозаменяемы с объектами своих родительских классов, т. е. любой дочерний класс должен вести себя точно так же, как родительский класс. Это также известно как «полиморфизм подтипов» и помогает разработчикам создавать более управляемый и надежный код.</p><h3>SOLID примеры на Python — принцип подстановки Барбары Лисков</h3><p>Принцип подстановки Барбары Лисков (Liskov Substitution Principle – LSP) гласит, что объекты должны быть заменяемыми экземплярами их базовых типов без нарушения корректности программы. Давайте рассмотрим пример с классами Rectangle (Прямоугольник) и Square (Квадрат), чтобы проиллюстрировать принцип LSP:</p><p>В этом примере класс Rectangle представляет прямоугольник с методами для установки ширины и высоты, а также для получения площади.</p><p>Класс Square наследуется от Rectangle и переопределяет методы set_width() и set_height(). В случае квадрата ширина и высота всегда должны быть одинаковыми, поэтому при установке одного измерения класс Square автоматически устанавливает и другое измерение равным ему.</p><p>Однако данный пример нарушает SOLID принцип LSP. Рассмотрим следующий код:</p><p>В этом примере мы вызываем функцию print_area(), которая ожидает объект типа Rectangle. Когда мы передаем rectangle, результат площади правильный (20), потому что ширина и высота были установлены независимо. Однако, когда мы передаем square, ожидаемая площадь должна быть также 20, но фактически получаем 16. Это происходит из-за изменения поведения методов set_width() и set_height() в классе Square.</p><p>Таким образом, класс Square не является полностью заменяемым объектом для класса Rectangle, нарушая принцип LSP. Чтобы исправить это, мы можем пересмотреть дизайн иерархии классов, чтобы избежать нарушения принципа LSP, или использовать интерфейсы и абстракции для достижения корректного поведения при замене объектов.</p><h3>Резюмируя</h3><p>Принцип подстановки Лисков гласит, что любой экземпляр подтипа должен иметь возможность заменить экземпляр своего базового типа. Это означает, что подтипы не должны нарушать поведение своих базовых типов. По сути, базовый класс и любые подклассы должны быть взаимозаменяемыми без нарушения кода.</p><h2>Принцип разделения интерфейса</h2><p>Принцип разделения интерфейса довольно прост и гласит, что разработчики не должны полагаться на огромные классы с множеством методов. Вместо этого разработчикам следует создавать множество меньших классов с меньшим количеством методов. Это помогает уменьшить сложность кода и упростить управление программой.</p><h3>SOLID примеры на Python — принцип разделения интерфейса</h3><p>Принцип разделения интерфейса (Interface Segregation Principle – ISP) гласит, что клиенты не должны зависеть от интерфейсов, которые они не используют. Вместо создания общих интерфейсов следует создавать специфические интерфейсы для конкретных клиентов. Давайте рассмотрим пример с интерфейсами для различных устройств вывода ввода:</p><p>В этом примере у нас есть абстрактные классы InputDevice и OutputDevice, представляющие интерфейсы для устройств ввода и вывода соответственно. Затем мы определяем конкретные классы Keyboard, Mouse, Monitor и Printer, которые реализуют соответствующие методы.</p><p>Применяя SOLID  принцип ISP, мы разделяем интерфейсы на более специфические, чтобы клиенты могли зависеть только от интерфейсов, которые они используют. Например, если клиенту нужен только ввод с клавиатуры, он может зависеть только от интерфейса InputDevice и использовать класс Keyboard:</p><p>В этом примере функция process_input() принимает объект, реализующий интерфейс InputDevice, и обрабатывает его ввод. Здесь мы передаем объект Keyboard, который соответствует интерфейсу InputDevice. Таким образом, клиент зависит только от необходимого интерфейса и не зависит от лишних методов или классов.</p><p>Таким образом, принцип разделения интерфейса позволяет создавать более специфические интерфейсы для клиентов, избегая излишней связанности и улучшая модульность и переиспользуемость кода.</p><h3>Резюмируя</h3><p>Принцип разделения интерфейса гласит, что классы не должны зависеть от методов, которые они не используют. Это помогает улучшить читаемость кода, а также обеспечить сильную связность. Другими словами, классы, которые зависят от других классов, не должны зависеть больше, чем им нужно для выполнения своей работы.</p><h2>Принцип инверсии зависимостей</h2><p>Наконец, у нас есть принцип инверсии зависимостей, который предполагает, что классы не должны напрямую полагаться на другие классы, а вместо этого должны зависеть от абстракций. Это помогает значительно снизить сложность кода и сделать программу менее подверженной ошибкам.</p><h3>SOLID примеры на Python — принцип инверсии зависимостей</h3><p>Принцип инверсии зависимостей (Dependency Inversion Principle – DIP) гласит, что классы должны зависеть от абстракций, а не от конкретных реализаций. Высокоуровневые модули не должны зависеть от низкоуровневых модулей. Оба типа модулей должны зависеть от абстракций. Давайте рассмотрим пример с классами Notification и EmailSender, чтобы проиллюстрировать принцип DIP:</p><p>В этом примере класс User зависит от конкретной реализации EmailSender в качестве сервиса уведомлений. Это создает прямую связь между User и EmailSender, что делает классы сложнее для тестирования и внесения изменений.</p><p>Чтобы применить SOLID  принцип DIP, мы изменяем User, чтобы он зависел от абстракции Notification, а не от конкретной реализации:</p><p>Теперь User принимает объект notification_service, реализующий интерфейс Notification, через конструктор. Это позволяет передавать различные реализации уведомлений, такие как EmailSender или SMSNotification, без изменения самого User:</p><p>Теперь User зависит от абстракции Notification и может быть легко настроен для работы с различными реализациями уведомлений. Это уменьшает связанность между классами, делает их более гибкими и легкими для тестирования и модификации.</p><h3>Резюмируя</h3><p>Принцип инверсии зависимостей гласит, что код должен зависеть от абстракций, а не от конкретики. Это означает, что код не должен зависеть от конкретных реализаций, а скорее от абстракции, которая затем реализуется. Это помогает повысить гибкость и повторное использование кода.</p><h2>Заключение</h2><p>Принципы SOLID важны для понимания любого разработчика программного обеспечения. Важно помнить, что эти принципы следует применять при разработке программного обеспечения для достижения наилучших результатов. SOLID принципы могут помочь обеспечить простоту сопровождения и расширения кода в будущем. Хорошо понимая SOLID принципы, вы сможете писать код, который будет лучше структурирован, прост в масштабировании и обслуживании.</p><figure><img src="https://media.tproger.ru/uploads/2023/07/a8e7718b-d42f-4a59-bb21-84853c285ccb.jpg" alt="" /></figure><p>Примечание Рейтинг степени важности в разработке (0-10) является относительной оценкой и может различаться в зависимости от конкретной ситуации и контекста разработки.</p><p>Надеемся, что принципы SOLID на Python примерах оказались полезны и доступны для понимания.</p>]]></content:encoded>
    </item>
    <item>
      <title>Основные принципы ООП: абстракция в программировании</title>
      <link>https://tproger.ru/articles/osnovnye-principy-oop-abstrakciya-v-programmirovanii</link>
      <comments>https://tproger.ru/articles/osnovnye-principy-oop-abstrakciya-v-programmirovanii?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Марина Александровна]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/osnovnye-principy-oop-abstrakciya-v-programmirovanii</guid>
      <description><![CDATA[<p>Основные принципы ООП включают в себя абстракцию: что это такое, когда и для чего используется, а также наглядный пример абстракции в ООП.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/osnovnye-principy-oop-abstrakciya-v-programmirovanii">Основные принципы ООП: абстракция в программировании</a>»</p>]]></description>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Объектно-ориентированное программирование]]></category>
      <category><![CDATA[Основные принципы программирования]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 19 May 2023 08:15:05 GMT</pubDate>
      <content:encoded><![CDATA[<p>Абстракция — один из принципов ООП в программировании. По своей сути это процесс выделения общих характеристик и функциональности объектов или системы, игнорируя детали реализации.</p><h2>Для чего нужна абстракция в программировании?</h2><p>Абстракция позволяет разрабатывать программы на различных языках программирования, скрывая сложность и детали нижележащего кода. Это делается для упрощения сложных систем и концепций, чтобы разработчики могли фокусироваться на основных аспектах проблемы и легче понимали код.</p><h2>Преимущества абстракции в ООП</h2><p>В объектно-ориентированном программировании абстракция играет важную роль. Она позволяет создавать абстрактные классы и интерфейсы, которые определяют общие свойства и методы, не зависящие от конкретной реализации. Преимущества абстракции ООП включают:</p><ol><li>Упрощение сложности: абстракция в программировании позволяет скрыть детали реализации и сосредоточиться на ключевых аспектах системы. Это помогает упростить понимание и поддержку кода.</li><li>Модульность: возможность разбить систему на модули или классы, которые могут работать независимо друг от друга. Это способствует повторному использованию кода и улучшает масштабируемость проекта.</li><li>Повышение безопасности: абстракция позволяет скрыть некоторые детали реализации, что делает код более безопасным и защищенным. Внешние компоненты не имеют прямого доступа к внутренним деталям объекта или системы.</li></ol><h2>Пример абстракции в ООП</h2><p>В качестве примера реализуем абстрактный класс «Фигура» и его наследников на языке Java:</p><p>В этом примере абстрактный класс Shape содержит общие свойства и методы для всех фигур. У него есть абстрактные методы getArea() и getPerimeter(), которые должны быть реализованы в наследниках. Классы Circle и Rectangle наследуют абстрактный класс Shape и реализуют абстрактные методы в соответствии с логикой для каждой фигуры.</p><p>В методе main() создаются объекты Circle и Rectangle, которые вызывают метод printInfo(), чтобы вывести информацию о каждой фигуре, включая цвет, площадь и периметр.</p><p>Пример показывает, как абстракция в ООП позволяет определить общий интерфейс (абстрактный класс) и реализовать его в конкретных классах, обеспечивая гибкость и повторное использование кода.</p><p>Также держите полезную <a href="https://tproger.ru/translations/oop-principles-cheatsheet/">шпаргалку по принципам ООП</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Python для начинающих: дорожная карта в 2023 году</title>
      <link>https://tproger.ru/articles/python-roadmap-2023-ljn8jvxfj</link>
      <comments>https://tproger.ru/articles/python-roadmap-2023-ljn8jvxfj?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Марина Александровна]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/python-roadmap-2023-ljn8jvxfj</guid>
      <description><![CDATA[<p>Полноценный курс Python для начинающих, который станет прекрасным самоучителем. Делаем начало программирования на Python лёгким.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/python-roadmap-2023-ljn8jvxfj">Python для начинающих: дорожная карта в 2023 году</a>»</p>]]></description>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Основные принципы программирования]]></category>
      <category><![CDATA[Партнёрский материал]]></category>
      <category><![CDATA[Планы обучения]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 15 Mar 2023 07:49:34 GMT</pubDate>
      <content:encoded><![CDATA[<p>Разберем обучение Python с нуля на примере дорожной карты, которую мы подготовили совместно с онлайн-университетом <a href="https://tprg.ru/Arx4">Skypro</a>. Данная статья — это полноценный гайд разработчика на Python с нуля до Junior. Он подойдет и тем, кто уже начал изучение, и тем, кто только задумывается о программировании. Если же сомневаетесь в своих силах — обратите внимание на <a href="https://tprg.ru/8RvU">курс «Python-разработчик»</a> от Skypro.</p><p>Эта статья — часть нашего <a href="https://tproger.ru/articles/python--polnyj-putevoditel-dlya-razrabotchika">полного путеводителя по Python</a>.</p><ol><li><a href="https://tproger.ru/#part1">Основы Python</a></li><li><a href="https://tproger.ru/#part2">Синтаксис Python</a></li><li><a href="https://tproger.ru/#part3">Продвинутый Python</a></li><li><a href="https://tproger.ru/#part4">Популярные библиотеки и фреймворки Python — 2023</a></li><li><a href="https://tproger.ru/#part5">Дополнительные материалы</a></li><li><a href="https://tproger.ru/#part6">Где и как изучать Python?</a></li></ol><h2>Основы Python</h2><h3>Установка Python</h3><p>Для начала перейдите на <a href="https://www.python.org/downloads/">официальный сайт Python</a> и загрузите последнюю версию Python для Windows, Linux, MacOS или другой операционной системы.</p><h4>Как установить Python на Windows</h4><p>Выберите на сайте <a href="https://www.python.org/downloads/windows/">Python для Windows</a>, скачайте и запустите загруженный файл. Если у вас на компьютере установлена более ранняя версия языка Python 2 — просто удалите ее и скачайте Python 3.</p><p>Выберите путь и установите программу:</p><figure><img src="https://media.tproger.ru/uploads/2023/03/f667de6c-fb09-4be0-9c75-7ccd2ea9ff52.jpg" alt="" /></figure><p>Проверьте, что всё работает:</p><ul><li>запустите cmd;</li><li>введите python;</li><li>если всё хорошо, командная строка выведет версию языка.</li></ul><p>По умолчанию будут установлены:</p><ol><li>Интегрированная среда разработки IDLE.</li><li>Документация.</li><li>Менеджер пакетов pip.</li><li>Стандартный набор тестов.</li><li>Python Launcher.</li></ol><h4>Как установить Python на Linux</h4><p>Для большинства сборок Linux Python идет из коробки. Проверить это можно так — откройте окно терминала и выполните команды:</p><ul><li>python — version;</li><li>python2 — version;</li><li>python3 — version.</li></ul><figure><img src="https://media.tproger.ru/uploads/2023/03/bc400058-135f-412e-a403-75a8e353adb2.png" alt="" /></figure><p>Если по какой-либо причине на вашей системе стоит старая версия, вы можете самостоятельно скомпилировать <a href="https://www.python.org/downloads/source/?roistat_visit=12724225">последнюю версию Python</a>.</p><h4>Как установить Python на MacOS</h4><p>Python также входит в сборку MacOS, и его можно запустить через терминал. Если же вы хотите установить самую последнюю версию — она доступна <a href="https://www.python.org/downloads/macos/">по ссылке</a>.</p><h3>Рабочее окружение</h3><p>Обучение языку Python начинается с настройки рабочего окружения.</p><h4>Среды разработки Python</h4><p>Вот лишь несколько наиболее популярных Python IDE:</p><ol><li><a href="https://www.jetbrains.com/pycharm/">PyCharm</a> — это IDE, разработанная компанией JetBrains. Она предоставляет интегрированную среду для разработки Python-приложений, включая поддержку Django, Flask и других фреймворков. PyCharm имеет богатый набор функций, включая отладчик, инструменты для анализа кода и удобный интерфейс.</li><li><a href="https://code.visualstudio.com/">Visual Studio Code</a> — это легкий, быстрый и невероятно гибкий редактор кода, доступный на всех платформах. Отличный выбор для программирования на Python. Многофункциональная IDE включает в себя интегрированный отладчик, рефакторинг, автодополнение кода, etc.</li><li><a href="https://www.spyder-ide.org/">Spyder</a> — IDE, которая является частью научно-вычислительной среды Anaconda. Есть множество инструментов для работы с научными вычислениями, включая визуализацию данных, анализ и т. д. Spyder имеет встроенную консоль IPython, которая облегчает отладку и выполнение кода.</li><li><a href="https://www.sublimetext.com/">Sublime Text</a> — легкий редактор кода, который может быть расширен с помощью плагинов и расширений для поддержки Python. Простой интерфейс, автодополнение кода и другие полезные функции.</li><li><a href="https://github.blog/2022-06-08-sunsetting-atom/">Atom</a> — этот простой на первый взгляд редактор обладает богатым функционалом. Данная среда написана разработчиками GitHub с использованием Electron — фреймворка для создания кросс-платформенных десктопных приложений средствами HTML, CSS и JavaScript.</li></ol><h4>Виртуальное окружение Python</h4><p>Вы можете столкнуться с тем, что вам захочется поэкспериментировать с библиотеками или модулями. Чтобы не засорять рабочее окружение, вы будете создавать отдельный проект со своими модулями под каждую задачу.</p><p>ПО для работы с виртуальным окружением можно разделить на то, что входит в стандартную библиотеку Python, и то, что не входит в нее. Наиболее популярные инструменты:</p><ul><li>Virtualenv — один из самых популярных инструментов для создания виртуальных окружений. Прост в установке и использовании.</li><li>Pyenv — тулза для изоляции версий Python. pyenv. Полезен, когда на одной машине нужно сразу несколько версий интерпретатора для тестирования.</li><li>Virtualenvwrapper — обертка для virtualenv, позволяющая хранить все изолированные окружения и управлять ими.</li><li>Venv — входит в стандартную библиотеку Python. Появился в версии Python 3 и по функционалу похож на virtualenv.</li></ul><h3>Git</h3><p>Обучение языку Python с нуля включает также изучение систем контроля версий — VCS. Именно они позволяют отслеживать и фиксировать любые изменения в коде: вы можете восстановить код в случае сбоя или откатить до более ранних версий.</p><p><a href="https://git-scm.com/">Git</a> — самая популярная VCS и мастхэв-инструмент для взаимодействия нескольких разработчиков на одном проекте. Подробнее о данной VCS вы можете почитать в нашем <a href="https://tproger.ru/translations/git-style-guide/">руководстве по командной разработке с Git</a>.</p><h3>HTTP-протоколы</h3><p>HTTP-протоколы в Python-разработке используются для создания веб-приложений и веб-сервисов, которые могут обмениваться данными с пользователем.</p><p>Для работы с HTTP-протоколами в Python используется стандартная библиотека <a href="https://docs.python.org/3/library/urllib.html">urllib</a>, которая предоставляет функции для отправки HTTP-запросов и обработки ответов.</p><p>Также для работы с HTTP-протоколами можно использовать более специализированные библиотеки, такие как requests или aiohttp:</p><ol><li>Библиотека <a href="https://pypi.org/project/requests/">requests</a> облегчает отправку HTTP-запросов и обработку ответов, предоставляя более высокоуровневый интерфейс для работы с протоколом. Она позволяет создавать сеансы, работать с куками, заголовками и параметрами запроса, а также обрабатывать различные типы ответов.</li><li>Библиотека <a href="https://docs.aiohttp.org/en/stable/">aiohttp</a> предоставляет асинхронный интерфейс для работы с HTTP-протоколами, что позволяет создавать более производительные и масштабируемые приложения. С ней можно использовать <a href="https://docs.python.org/3/library/asyncio.html">asyncio</a> для создания событийных циклов, работать с потоками и обрабатывать различные типы запросов/ответов.</li></ol><p>Знание HTTP-протоколов важно для разработчиков на Python, так как это позволяет им создавать эффективные и безопасные веб-приложения.</p><h3>Базы данных</h3><p>Даже начинающий Python-разработчик должен быть в курсе баз данных: а именно знать, <a href="https://www.oracle.com/cis/database/what-is-database/">что это такое</a> и <a href="https://tproger.ru/translations/sql-recap/">как с ними работать</a>.</p><p>Одной из главных особенностей использования баз данных в Python является наличие множества библиотек, позволяющих взаимодействовать с различными типами <a href="https://ru.wikipedia.org/wiki/%D0%A1%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D0%B0_%D1%83%D0%BF%D1%80%D0%B0%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F_%D0%B1%D0%B0%D0%B7%D0%B0%D0%BC%D0%B8_%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85">СУБД</a>. Вот лишь некоторые инструменты для работы с базами данных в Python:</p><ul><li>SQLAlchemy.</li><li>Django ORM.</li><li>Peewee.</li><li>Pony ORM.</li></ul><p>Разработчик может выбирать тот инструмент, который лучше всего подходит в конкретной ситуации.</p><p>Например, <a href="https://www.sqlalchemy.org/">SQLAlchemy</a> предоставляет возможность создания SQL-запросов с помощью объектов Python, что позволяет создавать сложные запросы и обращаться к базам данных, используя ORM.</p><p><a href="https://docs.djangoproject.com/en/4.1/topics/db/queries/">Django ORM</a> является частью веб-фреймворка Django и предоставляет более простой интерфейс для работы с БД, не требуя написания SQL-запросов вручную.</p><p>Кроме того, Python поддерживает работу с базами данных NoSQL, такими как <a href="https://www.mongodb.com/">MongoDB</a> и <a href="https://redis.io/">Redis</a>, что может быть полезно для работы с большими объемами данных и их обработки в режиме реального времени. Библиотеки для работы с такими БД также существуют и могут быть найдены в <a href="https://pypi.org/">PyPI</a>.</p><h2>Синтаксис Python</h2><p>Синтаксис Python простой и легкочитаемый, из-за чего данный язык может похвастаться низким порогом входа. Именно поэтому Python преобладает в программах онлайн- и офлайн-обучения.</p><p>Начните практику с наших <a href="https://tproger.ru/problems/python-3-exercises-for-beginners-geekbrains">задач по Python для начинающих</a>.</p><h3>Типы данных</h3><p>Python поддерживает различные типы данных, включая:</p><ol><li>Числовые типы: целые числа (int), числа с плавающей точкой (float), комплексные числа (complex).</li><li>Строки: последовательности символов, определенных в кавычках (str).</li><li>Булевы значения: TRUE и FALSE (bool).</li><li>Списки: упорядоченные изменяемые коллекции объектов (list).</li><li>Кортежи: упорядоченные неизменяемые коллекции объектов (tuple).</li><li>Множества: неупорядоченные коллекции уникальных элементов (set).</li><li>Словари: неупорядоченные изменяемые коллекции пар «ключ — значение» (dict).</li></ol><p>Одной из ключевых особенностей типов данных в Python является то, что переменные не требуют объявления типа явным образом. Тип переменной определяется автоматически во время выполнения программы. Кроме того, Python также поддерживает динамическую типизацию: это означает, что переменные могут изменять свой тип во время выполнения программы.</p><h3>Операторы и приоритетность операторов</h3><p>Операторы Python делятся:</p><ol><li>На арифметические операторы.</li><li>Операторы сравнения.</li><li>Операторы присваивания.</li><li>Логические операторы.</li><li>Операторы принадлежности.</li><li>Операторы тождественности.</li><li>Битовые операторы.</li></ol><p>Операторы сравнения, арифметические, условные операторы и циклы имеют стандартный синтаксис, используемый во многих других языках программирования. Подробнее об операторах Python на примерах вы можете узнать из <a href="https://www.youtube.com/watch?v=xCiUykOSe-g">данного видео</a>.</p><p>Кроме того, Пайтон разработчик с нуля должен понимать, как работает приоритетность операторов. В выражении интерпретатор Python выполняет операторы с более высоким уровнем приоритета первыми. И за исключением оператора возведения в степень (**) они выполняются слева направо.</p><p>Следующая таблица демонстрирует приоритетность — самые приоритетные операции вверху, внизу — с низким приоритетом:</p><figure><img src="https://media.tproger.ru/uploads/2023/03/4af1af3a-8e28-4b0a-9175-1e563a639594.jpg" alt="" /></figure><h3>Переменные</h3><p>Переменные предназначены для хранения данных. Название переменной в Python должно начинаться с алфавитного символа или со знака подчеркивания, может содержать алфавитно-цифровые символы и знак подчеркивания.</p><p>В Python применяется два типа наименования переменных: camelCase и underscore_notation.</p><p>Оба варианта будут верны, но учтите, что это разные переменные. Также надо учитывать регистрозависимость, поэтому переменные onlineschool и Onlineschool будут представлять разные объекты.</p><p>Закрепим, что переменные в Python объявляются без явного указания их типа, а тип переменной определяется автоматически во время выполнения программы. Плюс переменные могут изменять свой тип во время выполнения программы.</p><p><a href="https://www.w3schools.com/python/python_variables.asp">Подробнее о переменных</a></p><h3>Отступы</h3><p>Если в других языках программирования отступы в коде предназначены только для удобочитаемости, то в Python они очень важны.</p><p>Пример:</p><p>Однако в следующем случае вы получите ошибку:</p><p>Учитывайте это при написании программ.</p><h3>Логические операторы</h3><p>Язык программирования Python очень прост для обучения с нуля, ведь его синтаксис схож с синтаксисом других ЯП. Это касается и логических операторов:</p><ul><li>and — возвращает True, если оба утверждения верны;</li><li>or — возвращает True, если хоть одно из утверждений верно;</li><li>not — обратный результат, возвращает False, если результат верен.</li></ul><h3>Условия Python и операторы if-else</h3><p>Python поддерживает обычные логические условия из математики:</p><ol><li>Равно: а == b</li><li>Не равно: а != b</li><li>Меньше, чем: а &lt; b</li><li>Меньше или равно: a &lt;= b</li><li>Больше, чем: а &gt; b</li><li>Больше или равно: a &gt;= b</li></ol><p>Эти условия можно использовать несколькими способами, чаще всего в операторах if и циклах.</p><p>А вот пример использования оператора if:</p><p>В этом примере две переменные, a и b, используются как часть оператора if, чтобы проверить, больше ли b, чем a. Поскольку a = 33, b = 200, мы знаем, что 200 больше 33, поэтому выводим на экран b больше чем a.</p><p>Ключевое слово elif — это способ Python сказать: «Если предыдущие условия неверны, попробуйте это условие».</p><p>В примере a равно b, поэтому первое условие неверно, но условие elif истинно, поэтому мы выводим на экран a и b равны.</p><p>Ключевое слово else улавливает всё, что не улавливается предыдущими условиями:</p><p>В этом примере a больше, чем b, поэтому первое условие неверно, также условие elif неверно, поэтому мы переходим к другому условию и выводим на экран a больше чем b.</p><h3>Циклы</h3><p>Любой курс Питон разработчика включает изучение циклов. Циклы позволяют выполнять некоторое действие, в зависимости от соблюдения условия. В языке Python есть следующие типы циклов:</p><ul><li>while;</li><li>for.</li></ul><p>С помощью цикла while мы можем выполнять набор операторов, пока условие истинно:</p><p>Цикл for используется для перебора последовательности (то есть списка, кортежа, словаря, набора или строки). С помощью цикла for мы можем выполнить набор операторов один раз для каждого элемента в списке, кортеже, наборе и т. д. Пример использования:</p><p>Подробнее о <a href="https://tproger.ru/explain/tipy-ciklov-v-jazykah-programmirovanija-for-foreach-while-i-do-while/">циклах в программировании</a>.</p><h3>Функции</h3><p>Python также поддерживает функции, которые могут принимать аргументы и возвращать значения. Функции в Python могут быть определены как встроенные (built-in) или определены пользователем.</p><p>Обычно функция определяется с помощью инструкции def. Определим простейшую функцию:</p><p>Инструкция return говорит, что нужно вернуть значение. В нашем случае функция возвращает сумму x и y. Теперь мы можем ее вызвать:</p><p>Функция может принимать произвольное количество аргументов или не принимать их вовсе. Также распространены функции с произвольным числом аргументов, функции с позиционными и именованными аргументами, обязательными и необязательными.</p><h3>Классы</h3><p>Классы — это основа объектно-ориентированного программирования (ООП) в Python. Классы используются для определения объектов, которые могут иметь свойства (переменные) и методы (функции).</p><p>В Python классы создаются с помощью ключевого слова class. Вот пример простого класса Person с одним свойством (имя) и одним методом (приветствие):</p><p>В этом примере метод __init__ является конструктором класса и инициализирует свойство name. Метод say_hello выводит приветствие, используя значение свойства name. Чтобы создать экземпляр класса Person, нужно вызвать его конструктор с соответствующим аргументом.</p><p>Например, чтобы создать объект person с именем Алиса, мы можем написать следующий код:</p><p>Python-разработчик, написавший таким образом класс с нуля, может вызывать методы объекта person, например:</p><p>Классы в Python также поддерживают наследование, что позволяет создавать новые классы на основе существующих, добавлять новые свойства и методы. Кроме того, Python поддерживает множественное наследование, которое позволяет создавать классы, наследующие свойства и методы от нескольких родительских классов.</p><h3>Методы</h3><p>В Python методы — это функции, которые определены внутри класса и могут вызываться в экземплярах этого класса. Методы могут изменять состояние объекта, обращаться к его свойствам и выполнять другие операции.</p><p>Вот пример класса Rectangle, который имеет два свойства (ширина и высота) и два метода (вычисление площади и периметра):</p><p>Метод __init__ является конструктором класса и инициализирует свойства width и height. Методы area и perimeter вычисляют площадь и периметр прямоугольника, используя значения свойств width и height.</p><p>Чтобы создать экземпляр класса Rectangle, нужно вызвать его конструктор с соответствующими аргументами. Например, чтобы создать объект rect с шириной 10 и высотой 5, мы можем написать следующий код:</p><p>Теперь можно вызывать методы объекта rect, например:</p><p>Методы могут принимать аргументы, как и обычные функции. Они также могут вызывать другие методы или обращаться к другим свойствам объекта. Кроме того, Python поддерживает статические методы и методы класса, которые могут быть вызваны на классе, но не в его экземплярах.</p><h2>Продвинутый Python</h2><p>На этом обучение Python-разработчика не заканчивается. Необходимо понимать, как работает язык, а для этого важно углубиться в продвинутые аспекты разработки на нём.</p><p>Продвинутая тема для следующего шага — <a href="https://tproger.ru/translations/demystifying-decorators-in-python">декораторы в Python</a>.</p><h3>Функциональное программирование</h3><p>Функциональное программирование в Python — это подход к написанию программ, основанный на функциях высшего порядка, неизменяемости данных и отсутствии побочных эффектов.</p><p>Python поддерживает функциональное программирование, в том числе использование анонимных функций (lambda-функций), функций высшего порядка, списковых включений, генераторов, функциональных конструкций, таких как map(), reduce() и filter() и т. д.</p><p>Пример использования функции высшего порядка map(), которая применяет функцию к каждому элементу списка и возвращает новый список с результатами:</p><p>Также в Python есть поддержка генераторов, которые позволяют создавать ленивые последовательности значений. Пример использования генератора для создания бесконечной последовательности чисел Фибоначчи:</p><p>Эти функции и конструкции позволяют писать более чистый, краткий и выразительный код, который легче поддерживать и тестировать. Но функциональное программирование не всегда является лучшим выбором, особенно в случаях, когда нужно работать с изменяемыми данными или писать производительный код.</p><h3>Лямбда-функции</h3><p>Лямбда-функция в Python — это анонимная функция, которая может быть определена в одной строке кода без использования ключевого слова def. Лямбда-функция возвращает результат выражения, которое находится после оператора :.</p><p>Пример использования лямбда-функции для возведения числа в квадрат:</p><p>Лямбда-функции могут использоваться вместо именованных функций, когда нужно определить функцию внутри другой функции или передать функцию в качестве аргумента в другую функцию.</p><p>Пример использования лямбда-функции в качестве аргумента map() для возведения каждого элемента списка в квадрат:</p><p>Лямбда-функции могут быть полезны, когда нужно определить простую функцию без необходимости давать ей имя или использовать ее в других местах программы. Но если функция достаточно сложная или будет использоваться многократно, лучше определить ее с помощью ключевого слова def.</p><h3>Мапы, списки, коллекции</h3><p>Список — это упорядоченная коллекция элементов, которые могут быть изменены. Список может содержать элементы разных типов, и доступ к ним осуществляется по индексу. Например:</p><p>Коллекции — это контейнеры, которые содержат набор элементов. Они бывают двух типов: изменяемые (списки, множества, словари) и неизменяемые (кортежи, строки). Например, множество — это коллекция, которая не содержит дублей и может быть изменена:</p><p>Map — функция, которая принимает функцию и последовательность и применяет данную функцию к каждому элементу последовательности, возвращая новую последовательность с результатом. Пример:</p><h3>Ошибки и их обработка в Python</h3><p>В Python при выполнении программ могут возникать ошибки или исключения, такие как деление на ноль, попытка доступа к несуществующему индексу списка, неверное имя переменной и другие. Для этого в программах обучения на Питон разработчика с нуля предусматривается блок разбора исключений через конструкцию try-except.</p><p>Try-except позволяет «поймать» исключение и выполнить действия по его обработке, не прерывая выполнение программы. Конструкция выглядит следующим образом:</p><p>В данном примере, если при выполнении кода произойдет деление на ноль, будет вызвано исключение ZeroDivisionError, и выполнение программы перейдет к блоку except.</p><p>Также можно использовать конструкцию try-except для обработки нескольких типов исключений одновременно:</p><h3>I/O</h3><p>В Python для работы с I/O (вводом/выводом) существуют встроенные функции и модули. Например, функции print и input используются для ввода и вывода данных в консоль:</p><p>Для работы с файлами в Python есть функция open(), которая открывает файл на чтение или запись. Функция возвращает объект типа file, который позволяет производить операции с файлом:</p><p>Кроме того, в Python есть множество модулей для работы с различными форматами данных, такими как CSV, JSON, XML и другими:</p><h3>Регулярные выражения</h3><p>Регулярками называются шаблоны, которые используются для поиска соответствующего фрагмента текста и сопоставления символов. Подробнее о <a href="https://tproger.ru/translations/regular-expression-python/">регулярных выражениях в Python</a>.</p><h3>Тестирование</h3><p>Основная идея тестирования в Python заключается в том, чтобы написать тесты для каждого модуля или функции в отдельности и проверить, что они проходят успешно. Для этого используются специальные инструменты и библиотеки, которые позволяют запускать тесты автоматически и получать отчеты о результатах выполнения.</p><p>Две наиболее популярные библиотеки для тестирования на языке Python — unittest и pytest:</p><ol><li><a href="https://docs.python.org/3/library/unittest.html">Unittest</a> — это стандартный модуль тестирования в Python, с которым можно писать и запускать тесты для проверки работоспособности кода и который Python-разработчик должен знать с нуля. Он содержит множество методов и атрибутов для создания тестовых сценариев, утверждений и сбора результатов тестирования.</li><li><a href="https://docs.pytest.org/en/7.2.x/">Pytest</a> — библиотека тестирования в Python, которая обеспечивает более простой и элегантный подход к написанию тестов, чем unittest. Она предоставляет широкий спектр возможностей, включая автоматическое обнаружение и запуск тестов, улучшенный вывод результатов тестирования, множественные плагины и расширения, а также поддержку различных типов тестирования.</li></ol><h2>Популярные библиотеки и фреймворки Python — 2023</h2><h3>Data Science</h3><ul><li><a href="https://numpy.org/">NumPy</a> — библиотека для работы с массивами данных, матрицами и алгоритмами линейной алгебры.</li><li><a href="https://pandas.pydata.org/">Pandas</a> — библиотека для обработки и анализа данных, предоставляющая удобный интерфейс для работы с таблицами данных.</li><li><a href="https://matplotlib.org/">Matplotlib</a> — для визуализации данных, создания графиков, диаграмм и других типов графических представлений данных.</li><li><a href="https://scikit-learn.org/stable/">Scikit-learn</a> — содержит реализации многих алгоритмов машинного обучения, включая классификацию, регрессию, кластеризацию и другие.</li><li><a href="https://www.tensorflow.org/">TensorFlow</a> — обеспечивает мощные инструменты для создания, обучения и использования нейронных сетей.</li></ul><h3>Веб-разработка</h3><ul><li><a href="https://www.djangoproject.com/">Django</a> — высокоуровневый фреймворк для веб-разработки, обеспечивающий удобную абстракцию для создания веб-приложений, включая аутентификацию, авторизацию, работу с базами данных и многие другие функции.</li><li><a href="https://flask.palletsprojects.com/en/2.2.x/">Flask</a> — легкий и гибкий фреймворк для веб-разработки, который позволяет создавать быстрые и масштабируемые веб-приложения.</li><li><a href="https://www.sqlalchemy.org/">SQLAlchemy</a> — библиотека для работы с базами данных, обеспечивающая удобный и гибкий интерфейс для работы с различными типами баз данных.</li><li><a href="https://pypi.org/project/requests/">Requests</a> — библиотека для работы с HTTP-запросами, которая позволяет отправлять и получать данные с веб-серверов.</li><li><a href="https://pypi.org/project/beautifulsoup4/">Beautiful Soup</a> — библиотека для парсинга HTML- и XML-документов, которая обеспечивает удобный способ извлечения данных из веб-страниц.</li></ul><h3>Геймдев</h3><ul><li><a href="https://www.pygame.org/news">Pygame</a> — это библиотека для разработки 2D-игр на Python. Она содержит инструменты для управления графикой, звуком и событиями, такими как нажатия клавиш и мыши.</li><li><a href="https://www.panda3d.org/">Panda3D</a> — фреймворк для разработки 3D-игр на Python. Обеспечивает разработчикам высокую степень контроля над графикой и физикой игры, а также облегчает создание сложных 3D-сцен и объектов.</li><li><a href="https://api.arcade.academy/en/latest/">Arcade</a> — библиотека для разработки 2D-игр на Python. Предоставляет инструменты для работы с графикой, физикой и анимацией, а также поддерживает множество вспомогательных функций, таких как загрузка изображений и звуков.</li><li><a href="https://pypi.org/project/PyOpenGL/">PyOpenGL</a> — библиотека для работы с графикой OpenGL на Python. Предоставляет высокоуровневый интерфейс для создания 3D-объектов и сцен, множество инструментов для управления освещением, материалами и текстурами.</li></ul><h2>Дополнительные материалы</h2><h3>Книги</h3><h3>Подкасты</h3><ul><li><a href="https://podcast.python.ru/">https://podcast.python.ru/</a></li><li><a href="https://realpython.com/podcasts/rpp/">https://realpython.com/podcasts/rpp/</a> — для тех, кто владеет английским.</li></ul><p>Также можете посмотреть и послушать полезные подкасты от MoscowPython:</p><h3>Курсы</h3><p>Хотите быстро освоить весь базис, представленный в нашей дорожной карте? Это легко реализовать с онлайн-университетом Skypro, который выпускает уверенных разработчиков на Python. Специальный <a href="https://tprg.ru/8RvU">онлайн-курс «Python-разработчик»</a> с трудоустройством от Skypro предполагает обучение программированию на Python с нуля до профи.</p><p>Для тех, кто не хочет учиться в большой группе, предусмотрен тариф «Премиальный». Здесь будущий Python-разработчик получит индивидуальное обучение: тариф предусматривает мини-группы по пять человек.</p><p>В программу также добавлены уроки по английскому для IT-специалистов.</p><h2>Где и как изучать Python?</h2><p>Всё зависит от уровня самоорганизации. Самообучение хорошо подойдет людям с развитым тайм-менеджментом и возможностью дисциплинированно выполнять поставленные перед собой задачи без внешнего контроля.</p><p>Но у данного подхода есть существенный недостаток: ваш уровень знаний и навыков некому оценить. В этом случае можно оплачивать услуги ментора, который время от времени будет проверять ваши таски и давать советы.</p><p>Но есть ли смысл оплачивать одного специалиста, если есть возможность пройти полноценные курсы Python-разработчика, где вашим развитием будут заниматься опытные наставники?</p><p>Например, у SkyPro есть <a href="https://tprg.ru/8RvU">курс Python-разработчика</a> с нуля. За 10 месяцев вы освоите базу, познакомитесь с необходимыми инструментами на практике, создадите 7 пет-проектов для портфолио и сможете претендовать на позицию младшего разработчика. По окончанию курса вы получите диплом о профессиональной переподготовке, а карьерные консультанты помогут правильно составить резюме и подготовиться к вопросам рекрутеров на собеседованиях.</p><p>Мы же надеемся, что статья оказалась для вас полезной. Остались вопросы? Задайте их в комментариях.</p><p>Реклама ОАНО ДПО «СКАЕНГ» LjN8JvxFj</p>]]></content:encoded>
    </item>
    <item>
      <title>Подборка мемов от Tproger — Linux vs Windows, Java vs JavaScript и Новый год</title>
      <link>https://tproger.ru/articles/podborka-memov-ot-tproger-linux-vs-windows-java-vs-javascript-i-novyj-god</link>
      <comments>https://tproger.ru/articles/podborka-memov-ot-tproger-linux-vs-windows-java-vs-javascript-i-novyj-god?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Рафаил Агазода]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/podborka-memov-ot-tproger-linux-vs-windows-java-vs-javascript-i-novyj-god</guid>
      <description><![CDATA[<p>Собрали подборку лучших мемов про IT за первые недели января из наших каналов — IT Юмора в Телеграме и /dev/null в VK.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/podborka-memov-ot-tproger-linux-vs-windows-java-vs-javascript-i-novyj-god">Подборка мемов от Tproger — Linux vs Windows, Java vs JavaScript и Новый год</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Java]]></category>
      <category><![CDATA[Linux]]></category>
      <category><![CDATA[Windows]]></category>
      <category><![CDATA[Основные принципы программирования]]></category>
      <category><![CDATA[Системное администрирование]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 18 Jan 2023 12:52:59 GMT</pubDate>
      <content:encoded><![CDATA[<p>Собрали подборку лучших мемов за первые недели января из наших юмористических каналов — <a href="https://t.me/+ANwhTyPVDARhNzAy">IT Юмора</a> в Телеграме и <a href="https://vk.com/tnull">/dev/null</a> в VK.</p><p>Налейте любимый напиток в огромную кружку и украсьте этот день мемами, потому что It Is Wednesday My Dudes!</p><figure><img src="https://media.tproger.ru/uploads/2023/01/3c8b0882b68446b0ae1ffef0b441a9c9.jpeg" alt="" /></figure><h2>Сисадминов выбираем мы сами, но лучших оставляет время ☝️☝️☝️</h2><p>Ауф!</p><figure><img src="https://media.tproger.ru/uploads/2023/01/N0ULJeWFkM0.jpg" alt="" /></figure><p>Ауф:</p><figure><img src="https://media.tproger.ru/uploads/2023/01/Snimok-jekrana-ot-2023-01-18-14-22-01.png" alt="" /></figure><h2>20 лет опыта против 8 выходных дней</h2><p>Когда вернулся к работе после новогодних:</p><figure><img src="https://media.tproger.ru/uploads/2023/01/VNdINB4fnI8.jpg" alt="" /></figure><p>Не забывайте отдыхать:</p><figure><img src="https://media.tproger.ru/uploads/2023/01/Snimok-jekrana-ot-2023-01-18-14-24-08.png" alt="" /></figure><h2>«Иди на Stack Overflow дальше, это твоё распределение, это твой путь и твой горизонт познания»</h2><p>Когда познал всю кодовую базу:</p><figure><img src="https://media.tproger.ru/uploads/2023/01/bIMS2K9Qb_E.jpg" alt="" /></figure><h2>Вспомнить всё. Или дать функции понятное название</h2><p>Когда возвращаешься к коду спустя месяц и пытаешься вспомнить что каждая функция делает:</p><p>Называя функции понятно, вы помогаете не только коллегам, но и самому себе в будущем:</p><figure><img src="https://media.tproger.ru/uploads/2023/01/Snimok-jekrana-ot-2023-01-18-14-28-29.png" alt="" /></figure><h2>О важности work-life balance</h2><p>А техлид не спит, потому что он работает 20 часов в сутки:</p><figure><img src="https://media.tproger.ru/uploads/2023/01/WVqtYArgTTw.jpg" alt="" /></figure><p>Высыпайтесь как следует, иначе…</p><figure><img src="https://media.tproger.ru/uploads/2023/01/Snimok-jekrana-ot-2023-01-18-14-30-10.png" alt="" /></figure><h2>Сам себе сисадмин</h2><p>Сам не дашь — никто не даст:</p><figure><img src="https://media.tproger.ru/uploads/2023/01/ZNR3qtMnViY.jpg" alt="" /></figure><p>Ещё одна битва в бесконечной войне:</p><figure><img src="https://media.tproger.ru/uploads/2023/01/Snimok-jekrana-ot-2023-01-18-14-48-13.png" alt="" /></figure><h2>Windows vs Linux. Снова</h2><p>Потому что винда с тобой нянчится, а Linux обращается как с равным:</p><figure><img src="https://media.tproger.ru/uploads/2023/01/8-hYMDtPEIE.jpg" alt="" /></figure><p>Слишком плохо, чтобы быть неправдой:</p><figure><img src="https://media.tproger.ru/uploads/2023/01/Snimok-jekrana-ot-2023-01-18-14-50-31.png" alt="" /></figure><h2>Карьерный рост</h2><p>Нужно как-то ещё проскочить позиции мидла и джуна:</p><figure><img src="https://media.tproger.ru/uploads/2023/01/x_LYvVrjJm4.jpg" alt="" /></figure><p>В любом случае, всё проконтролирует продакт:</p><figure><img src="https://media.tproger.ru/uploads/2023/01/Snimok-jekrana-ot-2023-01-18-14-51-46.png" alt="" /></figure><h2>Программирование от чайников</h2><p>Разница между Java и JavaScript такая же как и между «панк» и «панкреатит»:</p><figure><img src="https://media.tproger.ru/uploads/2023/01/v-HZwMXQJOA.jpg" alt="" /></figure><p>Здесь же Java и JS встретились в реальной жизни:</p><figure><img src="https://media.tproger.ru/uploads/2023/01/Snimok-jekrana-ot-2023-01-18-14-53-35.png" alt="" /></figure><h2>Код всё ещё торт</h2><p>Синтаксический сахар:</p><figure><img src="https://media.tproger.ru/uploads/2023/01/PmBkZ5f14ZU.jpg" alt="" /></figure><p>Покрутить не помогает:</p><figure><img src="https://media.tproger.ru/uploads/2023/01/Snimok-jekrana-ot-2023-01-18-14-55-14.png" alt="" /></figure><p>Видели мемы прикольнее? Делитесь в комментариях! ?</p>]]></content:encoded>
    </item>
    <item>
      <title>Как стать самым быстрым программистом?</title>
      <link>https://tproger.ru/articles/kak-stat-samym-bystrym-programmistom</link>
      <comments>https://tproger.ru/articles/kak-stat-samym-bystrym-programmistom?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Кристина Дмитриевых]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kak-stat-samym-bystrym-programmistom</guid>
      <description><![CDATA[<p>Рассказали, как можно оптимизировать вашу работу, как быстрее писать код и как качественнее его проверять, на примере команды HP.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kak-stat-samym-bystrym-programmistom">Как стать самым быстрым программистом?</a>»</p>]]></description>
      <category><![CDATA[Основные принципы программирования]]></category>
      <category><![CDATA[Автоматизирование]]></category>
      <category><![CDATA[Гостевая публикация]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 28 Oct 2022 11:22:18 GMT</pubDate>
      <content:encoded><![CDATA[<p>Это — вторая серия проекта «Код Раковского», где Александр Раковский, Senior Java разработчик компании ITentika, рассказывает о том, что считает важным и интересным в сфере программирования. Первый выпуск можно посмотреть <a href="https://tproger.ru/articles/kod-kak-u-senora-refaktoring/">здесь</a>.</p><p>На первый взгляд, стать самым быстрым программистом — отличная цель. Кто успевает сделать больше фич, тот и приносит компании больше денег, да? А кто приносит больше денег, тому и самую большую зарплату, правильно?</p><p>Уверен, кто-то сейчас ухмыльнулся, вспомнив своего коллегу, который всех в команде бесил своими быстрофиксами и костылями. Похоже, компаниям важны не столько быстрые программисты, сколько быстрые команды.</p><p>Ну хорошо, а как замерить скорость программиста или команды?</p><p>Можно вводить разные странные метрики: число строк кода, число сторипоинтов или доставленных фич. Совершенно очевидно, что эти метрики никуда не годятся. При желании любая команда легко может ими манипулировать: писать сотни тысяч строк кода, который никогда не запустится, закладывать в оценки тысячи сторипоинтов, дробить большие фичи на маленькие.</p><p>Давайте зайдем с другой стороны. Как можно оптимизировать вашу работу? Может, надо быстрее писать код? Или качественнее проверять код, чтобы потом меньше времени тратить на отладку? Может быть, стоит меньше времени проводить на митингах? Сколько вообще времени ваша команда тратит на самое главное — реализацию нового функционала?</p><h2>Кейс HP LaserJet</h2><p>В 2008 году компания Hewlett Packard обнаружила себя в трудной ситуации. Исследование показало, что на разработку нового функционала HP LaserJet у компании оставалось лишь 5% всего времени — все остальное съедали сложности, возникающие при разработке кода под множество устройств.</p><figure><img src="https://media.tproger.ru/uploads/2022/10/36b77851-50a8-4412-9c25-1d451582c508.jpg" alt="" /></figure><p>Это заставило компанию запустить большую перестройку процессов. Новые процессы базировались на идеях экстремального программирования.</p><p>Ключевые нововведения:</p><ul><li>Практически полностью автоматизированное тестирование;</li><li>четыре уровня проверки кандидата к релизу — от самых быстрых тестов с частотой до 12 прогонов в день до самых медленных с частотой до 1 раза в сутки;</li><li>автоматизация инфраструктуры и развертываний. Любые изменения вплоть до конфигураций проходили через систему контроля версий и тесты, откуда автоматически развертывались на стенды;</li><li>короткие циклы планирования вместо разработки больших проектов на много месяцев вперед;</li><li>и самое радикальное — разработка на одной ветке, или, как сейчас говорят, trunk based development. Каждый день в главную ветку вливались по 10-14 коммитов, что позволило предотвратить все варианты интеграционных конфликтов.</li></ul><figure><img src="https://media.tproger.ru/uploads/2022/10/39269f46-322a-4dcc-8fbd-a21de08afdf0.jpg" alt="" /></figure><p>В результате удалось добиться сокращения затрат на большинство активностей в разы. Это позволило высвободить время для инноваций: процент времени, затрачиваемого на новый функционал, увеличился с 5% до 40%.</p><figure><img src="https://media.tproger.ru/uploads/2022/10/9f2cacbf-9821-4382-b45c-52747d55fb9e.jpg" alt="" /></figure><p>Самое интересное тут то, что времени на тестирование не стало меньше, оно даже, наоборот, удвоилось: несмотря на то, что ручное тестирование сократилось в три раза, теперь 23% всего времени уходило на автоматические тесты. То есть, увеличив время на автоматическое тестирование и автоматизацию инфраструктуры, компания смогла тратить меньше на остальные активности. Таким образом скорость разработки выросла на невероятные 700%.</p><h2>Сильные и слабые команды</h2><p>Это не столько частный случай, сколько общая практика. В 2016 году организация DORA в ежегодном исследовании State of Devops опубликовала свои замеры.</p><p>Оказалось, что сильные команды тратили на переделывание и незапланированную работу на 22% меньше, а на новую работу на 29% времени больше, чем слабые. Через год разрыв увеличился: сильные команды стали тратить на переделывание и незапланированную работу на 26% меньше и на 44% больше на новую работу. Таким образом, сильные команды в среднем тратят почти в полтора раза больше времени на полезную работу.</p><figure><img src="https://media.tproger.ru/uploads/2022/10/dcb81d81-1aa4-4b04-a680-01400cf73928.jpg" alt="" /></figure><p>Дизайн исследования специально был построен таким образом, чтобы сильные команды объединял все тот же высокий уровень автоматизации тестирования и развертывания. В слабых командах же, напротив, значительно больше было ручной работы.</p><h2>Зачем нужна автоматизация</h2><p>Важно понимать, почему высокий уровень автоматизации, требуя в среднем больше затрат, чем ручная работа, освобождает больше времени на разработку нового функционала. Если вы вспомните собственный опыт, то совершенно точно согласитесь, что даже при разработке нового функционала большая часть времени у вас уходит на поиск ошибок и попытки заставить что-то работать. Дефекты могут скрываться как в коде, так и в самой конфигурации системы, о чем мы нередко забываем. И вот тут многоуровневые автоматические тесты становятся настоящим спасением, потому что именно они дают почти моментальную обратную связь, если где-то что-то сломалось.</p><p>Другой важный эффект: чем больше времени проходит с момента совершения ошибки до момента ее обнаружения, тем сложнее эту ошибку найти и исправить. Во-первых, возрастает объем изменений и непонятно, какое именно вызвало ошибку. Во-вторых, поверх этой ошибки нередко успевает вырасти целый конструкт, который плотно связан с ней, и починив одно, можно сломать другое.</p><h2>Ну хорошо, а кто должен писать все эти тесты?</h2><p>Здесь все то же исследование State Of Devops демонстрирует еще более удивительную связь: тесты, написанные тестировщиками или даже отдельными командами, как оказалось, ничем не помогают. Только тесты, написанные самими разработчиками, позволяют добиться повышения эффективности команды.</p><p>Почему так?</p><p>Тут, на самом деле, нет единого ответа. Статистика указывает на корреляцию, но не объясняет, в чем дело. Может быть, когда разработчики пишут тесты сами, не происходит классического расщепления ответственности за качество кода между кодерами и тестерами. Может быть, дело в том, что к таким тестам у самих программистов гораздо больше доверия. А возможно, здесь важна именно комбинация множества факторов.</p><p>Суть остается неизменной: разработчики, одержимые тестами, добиваются значительно лучших результатов, чем их более консервативные коллеги.</p><h2>Ладно, автоматизация важна. Как и что автоматизировать?</h2><p>Это крайне обширная тема, которая максимально подробно освещена в книге «Непрерывное развертывание программного обеспечения» (Continuous Delivery) авторства Дейва Фарли и Джеза Хамбла.</p><p>Давайте попробуем вкратце описать процесс разработки в условиях полной автоматизации тестирования, развертывания и инфраструктуры.</p><p>Центральным элементом такого процесса является паттерн «Конвейер развертывания» (Deployment pipeline). Согласно этому шаблону, каждый коммит, попавший в главную ветку в системе контроля версий, запускает старт этого конвейера.</p><figure><img src="https://media.tproger.ru/uploads/2022/10/1b718c78-d19f-4333-9108-7872a66ef10c.jpg" alt="" /></figure><p>На первом шаге код собирается и на нем запускаются быстрые тесты, не требующие развертывания приложения. Этот шаг должен длиться не более 5 минут, чтобы не сдерживать доступ остальной команды к ветке.</p><p>Согласно практике, в случае красных тестов у автора коммита есть 5 минут на то, чтобы восстановить работоспособность ветки — устранить проблему или откатить свои изменения до последней рабочей версии.</p><p>После успешного прохождения тестов вторым шагом в репозиторий публикуется собранный кандидат к релизу, например docker-контейнер или иной артефакт.</p><p>За этим следует шаг с развертыванием приложения на тестовый стенд и конфигурацией стенда. Здесь происходят все накаты миграций баз данных и все изменения настроек приложений.</p><p>Очень важно, что любые изменения конфигурации должны проходить исключительно через конвейер: это дает возможность откатиться на предыдущую версию конфигурации в случае каких-то проблем. Эта практика называется «Инфраструктура как код».</p><p>Ни в коем случае нельзя настраивать вашу инфраструктуру вручную. Это приводит к антипаттерну «Сервер-снежинка» (Snowflake Server), когда ваш сервер уникален и неповторим, подобно снежинке. Нет ни малейшей гарантии, что то, что работает на вашей снежинке, будет работать хоть где-то еще.</p><p>Следующим шагом конвейера обычно становится шаг автоматических приемочных тестов на развернутом приложении. Эти тесты проверяют, что все требования к приложению выполнены: на этом этапе гоняются бизнес-сценарии, нагрузочные тесты, UI-тесты, security-тесты, инфраструктурные тесты.</p><p>Лучшая практика тут — приложить все усилия, чтобы этот шаг длился не более часа, чтобы у команды была возможность прогнать за день несколько попыток в случае какого-то падения. Другая популярная практика — катать приемочные тесты по ночам — приводит к тому, что команда может проверить лишь одну гипотезу в день. Это нередко выливается в то, что конвейер краснеет на несколько недель, парализуя работу всей команды.</p><p>Другой совет — ни в коем случае не пишите функциональные тесты через UI. Функционал и UI меняются по разным причинам: функционал меняется из соображений бизнеса, UI — из соображений эстетики и эргономики. Приемочные тесты, написанные через UI, приведут к тому, что любое изменение графического интерфейса будет требовать переписывания огромного массива функциональных тестов и, как следствие, ваш UI навеки замрет в том состоянии, в котором вы его однажды зафиксировали своими тестами.</p><p>Последний шаг конвейера — развертывание приложения в промышленную эксплуатацию. Тут надо понимать следующую идею: развертывание в продакшн — это бизнес-решение. Может быть, нет никаких проблем в том, чтобы после каждого коммита разработчика в течение часа на продакшене была уже новая версия. Почему нет — если все тесты зеленые, то есть требования к приложению удовлетворены. Может быть, как в случае, например, с драйверами устройств, такое невозможно по определению.</p><p>Может даже быть такая ситуация, что некоторые приемочные тесты покраснели, но бизнес все равно заинтересован в развертывании: например, появление критически важного функционала для бизнеса оказывается приоритетнее, чем падение производительности и покраснение нагрузочных тестов.</p><p>И еще раз: все эти тесты, изменения инфраструктуры стенда, миграции, изменения самого конвейера заезжают в репозиторий вместе с функционалом, к которому они относятся. Необязательно в одном коммите, но обязательно должно выполняться условие — зеленые тесты означают, что система готова к релизу с точки зрения бизнеса, а зелеными они должны быть всегда. Лакмусовая бумажка: вы должны быть способны откатиться на коммит годичной давности с последующим развертыванием его через конвейер в продакшн.</p><p>Главный результат такой автоматизации: код непрерывно находится в готовом к развертыванию в продакшн состоянии, каждый новый коммит — новый релиз.</p><p>У кого-то вышеописанные процессы могут вызвать лишь ухмылку. Мол, в идеальном мире розовых единорогов такое бы, может быть, и было возможно. Отнюдь. Именно такого преобразования и удалось добиться компании HP Laser Jet и многим другим известным крупным компаниям. Но соглашусь, это высший пилотаж, требующий высокой грамотности разработчиков в области тестирования и автоматизации инфраструктуры. Впрочем, в этом и цель моего повествования: указать на ориентиры в профессиональном развитии специалиста.</p><h2>Ну хорошо, автоматизация рулит, договорились. Есть ли какие-то другие способы ускорить команду?</h2><p>Командам очень дорого обходится возрастающая со временем сложность системы. Каждая новая фича повышает ценность продукта в глазах пользователя — и, к сожалению, каждая новая фича ложится тяжелым грузом на плечи разработчиков, тестировщиков и техподдержки. Чем больше кода написано, тем сложнее добавлять новый.</p><p>Эта идея называется гипотезой выносливости дизайна, и вот уже больше полувека наша отрасль страдает от этой напасти — от проклятия кодовых баз, которые невозможно изменить из-за накопленной сложности. Бессменными спутниками таких систем становятся крайне запутанная структура кода и, самое страшное, опасные и дорогие баги, которые очень сложно исправлять.</p><p>Как с этим бороться? Есть три способа:</p><ol><li>Приоритизировать простоту (правило: пишем самое простое, что может работать).</li><li>Рефакторинг. Всегда оставляйте код в лучшем состоянии, чем вы его застали. Наряду с первой практикой это позволит вам не только не вносить новую сложность в систему, но и постоянно ее сокращать.</li><li>Постепенно заменяйте ручные операции (тестирование и развертывание) автоматизированными.</li></ol><h2>Но самое дорогое в разработке программного обеспечения — работать не туда</h2><p>Давайте рассмотрим конкретный пример компании, которая делала игры для Facebook. Однажды владелец компании пришел к разработчикам и сказал, что следующей большой фичей станут уровни и достижения для всех игр. Он принес с собой толстую папку диаграмм, чертежей, скриншотов и всего, что отдел маркетинга разработал для этой фичи.</p><p>Разработчики, посмотрев на это безобразие, выдали грубую оценку всей работы в 9 месяцев. Это был тоннель, в который команда бы въехала с одного конца и только через 9 месяцев, выехав с другого конца, узнала бы, был ли это успех или провал. Факт того, что команда могла выпускать новые релизы каждые пару недель, позволял не ждать 9 месяцев, чтобы проверить идею. Поэтому команда вернулась к заказчику с вопросом, как это решение может изменить чье-то поведение. Этот вопрос застал владельца врасплох:</p><p>—Что значит «изменить чье-то поведение»?—Если через 9месяцев, когда фича будет готова, все будут делать тоже, что исейчас, как будто ничего непроизошло, это будет провал?—Конечно, провал!—Тоесть кто-то должен что-то делать иначе?—Мыхотим, чтобы пользователи публиковали свои достижения в«Фейсбуке».—Уже лучше. Зачем?—Всмысле— зачем? Чтобы ихдрузья увидели эти посты.—Тоесть насамом деле мыхотим изменить поведение людей, которые неиграют вигру. Нухорошо, апочему это важно?—Если другие люди прочитают эти посты, то, возможно, они придут попробовать эту игру.—Хорошо, это еще одно изменение поведения, но все еще недает общей картины. Почему это важно?</p><figure><img src="https://media.tproger.ru/uploads/2022/10/9c6ab9ff-39e9-4e0d-9456-05ca4a840bc7.jpg" alt="" /></figure><p>Владелец был потрясен:</p><p>—Это глупый вопрос. Чем больше игроков, тем больше денег.—Хорошо, тоесть мыхотим больше игроков?—Конечно!—Насколько больше?—Чем больше, тем лучше.—Если спустя 9месяцев эта фича приведет 5 новых игроков, это будет успех?—Нет, конечно, мырассчитываем намиллион игроков.</p><p>Бинго! Эта цифра все время была в голове у владельца, но до разработки дошла лишь после этого разговора. В итоге первое, что требовалось сделать, — проверить, насколько идея жизнеспособна. За пару часов работы была реализована возможность опубликовать сообщение о победе в игровом турнире, однако оказалось, что за 3 дня никто так и не нажал кнопку «поделиться» — никто не хотел спамить своим друзьям сообщениями о какой-то игре в «Фейсбуке». Таким образом, это направление оказалось заблокировано.</p><p>Поступило другое предложение: теперь игрокам предоставили возможность приглашать друзей — и, о чудо, игроки начали приглашать друзей. Проблема лишь в том, что никто так и не пришел по ссылке. Отдел маркетинга предположил, что это из-за того, что в письме была плохо видна ссылка на игру. Вместо маленькой ссылки в тексте сделали большую кнопку «присоединиться» — и тут игроки пошли. Спустя две недели полировки механизма приглашений и введения разных бонусов реферальной программы у игры был миллион игроков.</p><p>Эта история позволяет сформулировать очень интересные выводы::</p><ul><li>Во-первых, невероятно важно, чтобы все участвующие в решении понимали проблему, которую они решают. То есть задача должна декларироваться не в виде фичи «уровни и достижения», а в виде тезиса о привлечении в игру миллиона новых игроков.</li><li>Во-вторых, очень важно ввести правильные способы измерения прогресса в достижении решения проблемы. В контексте предыдущего примера такими метриками стали количество игроков, число публикаций, число приглашений и пришедших по приглашениям игроков.</li><li>В-третьих, нужно ошибаться быстро и недорого. Важно помнить: никто точно не знает, как решить проблему, все только лишь высказывают гипотезы. Поэтому необходимо быстро отвергать нерабочие гипотезы, проверяя их на прочность c помощью метрик.</li></ul><p>Согласно исследованию компании Microsoft, 2/3 всего разработанного функционала в лучшем случае не принесло никакой пользы. Это означает, что можно иметь крутейшую архитектуру, в которой нет ни строчки лишнего кода и избыточной сложности, можно иметь невероятные процессы, в которых каждый коммит после прогона всех видов автотестов тут же деплоится в прод — но если вы работаете не туда, то 2/3 вашей работы в лучшем случае не принесут никакой пользы. И, как правило, такие фичи лежат в кодовой базе и мешаются под ногами, съедают время в прогонах тестов, техподдержке приходится поддерживать эти фичи до самого конца продукта — а ведь ими никто и не пользуется.</p><p>Получается, что компании выгоднее было бы, если бы вы эти 2/3 времени провели на пляже, ну или на горнолыжном курорте, или просто с родными…</p><h2>Вывод</h2><p>Подведем итоги:</p><ul><li>Автоматизация тестирования и инфраструктуры приводит к росту продуктивности в среднем в полтора раза. В отдельных случаях удавалось зафиксировать рост в 8 раз.</li><li>Центральным элементом такой автоматизации является шаблон «Конвейер развертывания». Главный результат: код непрерывно находится в готовом к развертыванию в продакшн состоянии, каждый новый коммит — новый релиз.</li><li>Другой проблемой становится трясина легаси-кода. Главные сдерживающие факторы — запутанная структура и дорогие баги. Главные лекарства — простой дизайн, рефакторинг и автотесты.</li><li>Самое дорогое препятствие к решению проблем бизнеса — неверно выбранный вектор работы. Очень важно, чтобы все участники процесса хорошо понимали цель, правильно выбирали метрики и работали в коротких циклах, позволяющих ошибаться быстро и дешево.</li></ul><p>На этом у меня все. Спасибо за внимание, пишите хороший код, тесты и до новых встреч.</p>]]></content:encoded>
    </item>
    <item>
      <title>Логические операторы: И, ИЛИ, РАВНО и другие</title>
      <link>https://tproger.ru/explain/logicheskie-operatory-i-ili-ravno-i-drugie</link>
      <comments>https://tproger.ru/explain/logicheskie-operatory-i-ili-ravno-i-drugie?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Олег Борисенков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/explain/logicheskie-operatory-i-ili-ravno-i-drugie</guid>
      <description><![CDATA[<p>Что такое логические операторы. Рассматриваем основные виды: И, ИЛИ, РАВНО и другие. Рассматриваем применение и основные задачи логических операторов ✔ Tproger</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/explain/logicheskie-operatory-i-ili-ravno-i-drugie">Логические операторы: И, ИЛИ, РАВНО и другие</a>»</p>]]></description>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Основные принципы программирования]]></category>
      <category><![CDATA[Коротко о главном]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 07 Dec 2020 11:04:16 GMT</pubDate>
      <content:encoded><![CDATA[<p>Логические операторы — это специальные символы (или сочетания символов), которые изменяют или комбинируют логические значения типа Boolean — true и false. Их используют чтобы создавать сложные условия, например в <a href="https://tproger.ru/explain/tipy-ciklov-v-jazykah-programmirovanija-for-foreach-while-i-do-while">циклах</a>.</p><h2>Общее описание логических операторов</h2><p>Логические операторы позволяют создавать условия, которые определяют поведение программы в зависимости от того, истинны или ложны входные данные. Программирование часто включает принятие решений — в каких случаях выполнять ту или иную операцию, а логические операторы являются ключевым инструментом для этого. Они помогают строить сложные логические выражения, используя более простые условия.</p><p>В зависимости от вида задачи, программисты используют операторы для сравнения значений, проверки истинности, объединения или исключения условий. Логические операторы делятся на несколько категорий, каждая из которых имеет свою специфику применения.</p><h2>Логические операторы</h2><h3>Оператор НЕ (!, not)</h3><p>Выражение вида !true равно false и наоборот. Оператор ! (не) инвертирует логическое значение, это унарный оператор (применяется к одному операнду), он помещается перед операндом. Если условие истинно, то результат применения оператора отрицания будет ложным, и наоборот. Например, если a = true, то выражение !a даст false.</p><p>Например:</p><h3>Оператор И (&amp;&amp;, and)</h3><p>Оператор &amp;&amp; — это оператор логического умножения. Выражение вида a &amp;&amp; b будет равно true только тогда, когда и а и b равны true. Если хотя бы один из операндов окажется ложным, результат будет false. Данный оператор используется, когда необходимо, чтобы выполнялись несколько условий одновременно.</p><h3>Оператор ИЛИ (||, or)</h3><p>Оператор || — это оператор логического сложения. Выражение a || b будет равно true, когда или a, или b (или оба) равен true. Это полезно, когда нужно проверить, выполняется ли хотя бы одно из нескольких условий.</p><p>В сложных условиях оператор || помогает упростить проверки, позволяя гибко обрабатывать ситуации, когда истинность только одного условия уже достаточна для выполнения действия.</p><h3>Оператор исключающее ИЛИ (^, xor)</h3><p>Выражение a ^ b будет равно true, когда только один из операндов имеет значение true. Подробно о применении xor рассказывается в статье <b>«</b>Практика применения XOR в программировании<b></b><b>»</b></p><h3>Операторы сравнения</h3><p>Операторы сравнения позволяют проверять равенство или неравенство между двумя значениями. Эти операторы часто используются в условных операторах, таких как while и for. Рассмотрим два ключевых оператора сравнения.</p><ol><li><b>Оператор эквивалентности (==)</b> — проверяет и сравнивает два значения. Выражение a == b будет равно true, когда a и b имеют одинаковые значения. Например, выражение 5 == 5 вернёт true, так как оба значения одинаковы.</li><li><b>Оператор неравенства (!=)</b> — проверяет, разные ли два значения. Выражение a != b будет равно true, когда a и b имеют разные значения. Пример: 5 != 3 вернёт true, так как числа не равны.</li></ol><figure><img src="https://media.tproger.ru/uploads/2020/12/bool.png" alt="«Логические операторы: И, ИЛИ, РАВНО и другие»" /></figure><p>Эти операторы используются для создания логического условия в программах, что позволяет динамически изменять поведение системы.</p><h2>Приоритет логических операторов</h2><ol><li>Оператор эквивалентности — ==, оператор неравенства — !=.</li><li>Оператор отрицания — !.</li><li>Оператор И — &amp;&amp;.</li><li>Оператор исключающее ИЛИ — ^.</li><li>Оператор ИЛИ — ||.</li></ol><p>Предлагаем вам пройти тест и узнать, насколько хорошо вы разобрались с логическими операторами:</p>]]></content:encoded>
    </item>
    <item>
      <title>Реактивное программирование простыми словами — объясняют эксперты</title>
      <link>https://tproger.ru/experts/reactive-programming-in-simple-words</link>
      <comments>https://tproger.ru/experts/reactive-programming-in-simple-words?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Никита Прияцелюк]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/experts/reactive-programming-in-simple-words</guid>
      <description><![CDATA[<p>Классический подход предполагает запрос, ожидание ответа и продолжение работы. Эксперты объясняют, чем от него отличается реактивная парадигма.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/experts/reactive-programming-in-simple-words">Реактивное программирование простыми словами — объясняют эксперты</a>»</p>]]></description>
      <category><![CDATA[Основные принципы программирования]]></category>
      <category><![CDATA[Реактивное программирование]]></category>
      <category><![CDATA[Ответы экспертов]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 16 Oct 2020 06:57:07 GMT</pubDate>
      <content:encoded><![CDATA[<p>В предыдущих статьях эксперты объяснили нам, что такое <a href="https://tproger.ru/experts/oop-in-simple-words/">объектно-ориентированное</a>, <a href="https://tproger.ru/experts/what-is-dynamic-programming/">динамическое</a>, <a href="https://tproger.ru/experts/imperative-and-declarative-programming/">декларативное и императивное</a> программирование. В этот раз узнали у них, что из себя представляет реактивное программирование.</p><p>Что такое реактивное программирование?</p><p>Классический подход к программированию предполагает выполнение запроса (к базе, сервисам и т.д.), ожидание ответа и продолжение работы. Во время ожидания поток простаивает. Масштабируют такие системы путём увеличения количества потоков, при этом вычисление оптимального количества потоков становится непростой задачей, так как сильно зависит от характера нагрузки, количества и качества ожиданий. И несмотря на это, избежать на 100% простоя ресурсов не удается.</p><p>В реактивном программировании обработка делится на большое количество небольших задач, выполнение каждой из которых оканчивается неким событием. На возникновение этого события реагирует соответствующий обработчик и выполняет свою задачу, опять генерирует событие и общий процесс обработки продолжается. Генерация события и реакция на него происходят асинхронно. Обработчик (их ещё называют акторами) выбирает следующее событие из очереди, обрабатывает и складывает событие в очередь другому обработчику. Если задача заключается в выполнении запроса в базе данных, то актор посылает запрос на сервер. После получения ответа будет сгенерировано событие с результатом запроса и запущен соответствующий актор.</p><p>Всё это позволяет более эффективно занять ресурсы полезной работой и управлять масштабированием. Повышается отзывчивость приложений. Кроме того, реактивное программирование помогает масштабироваться горизонтально.</p><p>Понятие реактивного программирования тесно связано с понятием модели распространения данных, которая бывает двух типов:</p><ol><li>pull-модель.</li><li>push-модель.</li></ol><p>На самом деле, эти модели вполне логичны и естественны, поэтому их проявления можно проследить даже в обычной жизни.</p><p>Допустим, Вася любит быть в курсе всех новостей по хоккею и поэтому периодически посещает тематические сайты в надежде обнаружить интересный контент.</p><p>Его коллеге — Пете — тоже интересен хоккей, но в отличие от Васи, Петя подписался на рассылку новостей и получает уведомления о важных событиях в хоккее.</p><p>С точки зрения программирования, Вася использует pull-модель: периодически просматривает источники данных, в то время как Петя — push-модель: занимается обработкой входящих сообщений.</p><p>Таким образом, реактивное программирование — это стиль написания кода, который упрощает реализацию приложений, основанных на push-модели.</p><p>На практике этот стиль применяется для обработки входящего потока данных, например:</p><ul><li>сообщения от пользователей;</li><li>уведомления об изменении расписания;</li><li>действия пользователя с интерфейсом и т.д</li></ul><p>Реактивное программирование — это подход к разработке ПО, который строится на реагировании на события и на распространении событий. При этом модель реакции на события предполагает возможность простого распространения этих или трансформированных событий далее по системе. Ярким примером реализации реактивного подхода может служить таблица Excel. В ней существует цепочка вычислений, разделённая на несколько ячеек: при изменении значения одной из ячеек в цепочке значения в зависимых ячейках пересчитываются автоматически.</p><p>В целом, идея реактивного программирования призвана упростить создание сложных систем. В сложной большой системе возникает значительное количество разнообразных событий, каждое требует определённого механизма реакции и обработки. При использовании реактивного программирования события объединяются в потоки, а компоненты системы являются обработчиками потока событий (и также в свою очередь могут являться генераторами событий). Таким образом, сколь ни была бы сложна система и сколько бы в ней ни было разнообразных событий, вся система строится по принципу генерации потоков событий и реакции на них. Подход в моделировании сложной системы позволяет обрабатывать каждое событие асинхронно и изолированно от других. Важно понимать при этом, что дизайн реактивной системы предполагает, что любая функциональность в системе реализуется с помощью событий и обработчиков. Благодаря этому достигается уменьшение связанности между компонентами системы, увеличение гибкости, упрощение масштабирования и повышение устойчивости систем.</p><p>Парадигма реактивного программирования включает отслеживание определенных событий и реагирование на них в асинхронных потоках данных. Эта идея предполагает, что существует источник событий и слушатель событий, который реагирует на события источника.</p><p>Суть реактивного программирования можно изобразить разными способами. Например, представим реку, течение которой несет несколько разноцветных объектов (мячей). Допустим, на берегу сидит человек, которому нужно выловить объекты с определенной характеристикой – только зеленые или красные. Если нам требуется запрограммировать подобную ситуацию, то реактивный подход – это то, что поможет нам оперировать потоками данных, получать данные, совершать математические вычисления и т.д. Работа с потоками требует от программиста определенного опыта и понимания, что и как комбинировать, какие операторы подходят для решения задачи.</p><p>Реактивный подход активно используется в Frontend-разработке и мобильной разработке, одним из его популяризаторов является Netflix.</p><p>Само название парадигмы наводит на мысль, что реактивное программирование подразумевает какую-либо реакцию на изменения. Так и есть: реактивное программирование удобно при создании интерфейсов и построении моделей систем, изменяющихся во времени.</p><p>Реальным применением этой парадигмы может стать веб-фреймворк, поддерживающий архитектуру MVC (Model-View-Controller), в котором при изменении модели изменяется поведение пользовательских представлений.</p><p>Предположим, есть модель Пользователь с полями Имя и Фамилия. Добавляем поле Отчество — и во всех местах, будь это форма на сайте или структура базы данных, происходят соответствующие изменения: форма при следующей генерации содержит новое поле, а для БД генерируется миграция.</p><p>В рамках парадигмы чаще всего используют функции обратного вызова (callback) и конструкции асинхронного программирования (конкретные виды зависят от языка). Также здесь задействуют события (events) или потоки (flows). Если в коде есть такие «следы», значит, с большой вероятностью здесь применяется концепция реактивного программирования</p><h3>Итак, что из себя представляет реактивное программирование?</h3><p>В реактивном программировании обработка делится на большое количество небольших задач, выполнение каждой из которых оканчивается неким событием. На событие реагирует обработчик, который выполняет свою задачу и снова генерирует событие. Генерация события и реакция на него происходят асинхронно.Идея реактивного программирования призвана упростить создание и масштабирование сложных систем.Примером реактивного подхода может служить таблица Excel. В ней существует цепочка вычислений, разделённая на несколько ячеек: при изменении значения одной из ячеек значения в зависимых ячейках пересчитываются автоматически.</p><p>Напоминаем, что вы можете <a href="https://docs.google.com/forms/d/e/1FAIpQLSdSanNvlfPRrSyQWfnoGPflSVwO4KctnjOdEKHzuxjCmFX2dA/viewform">задать свой вопрос</a> экспертам, а мы соберём на него ответы, если он окажется интересным. Вопросы, которые уже задавались, можно найти в списке выпусков <a href="https://tproger.ru/experts/">рубрики</a>. Если вы хотите присоединиться к числу экспертов и прислать ответ от вашей компании или лично от вас, то пишите на <a>experts@tproger.ru</a>, мы расскажем, как это сделать.</p>]]></content:encoded>
    </item>
    <item>
      <title>Императивное и декларативное программирование простым языком — объясняют эксперты</title>
      <link>https://tproger.ru/experts/imperative-and-declarative-programming</link>
      <comments>https://tproger.ru/experts/imperative-and-declarative-programming?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Никита Прияцелюк]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/experts/imperative-and-declarative-programming</guid>
      <description><![CDATA[<p>Императивные языки вроде Java, Python, JavaScript, C и C++ доминируют в индустрии. Эксперты объясняют суть обеих парадигм понятными словами.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/experts/imperative-and-declarative-programming">Императивное и декларативное программирование простым языком — объясняют эксперты</a>»</p>]]></description>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Основные принципы программирования]]></category>
      <category><![CDATA[Ответы экспертов]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 07 Oct 2020 06:41:07 GMT</pubDate>
      <content:encoded><![CDATA[<p>Начинающему программисту несложно запутаться в различных терминах — взять только объектно-ориентированное, динамическое, императивное и декларативное программирование. Спросили у экспертов, что из себя представляют два последних подхода.</p><p>Что такое императивное и декларативное программирование?</p><p>Императивные языки, такие, как Java, Python, JavaScript, C, C++ занимают доминирующее положение в индустрии ПО, соответственно императивное программирование — самое распространённое. Смысл его в том, что императивная программа содержит прямые указания, что должен сделать компьютер и в каком порядке должны выполняться инструкции. Этот подход легко понять программисту, а компилятору — легко породить достаточно эффективный код.</p><p>Декларативное программирование распространено не так обширно, как императивное, хотя оказывает большое влияние на мейнстрим. Смысл декларативного программирования в том, что программы пишут в виде некоторых ограничений и правил. Логические языки, такие, как Пролог, предлагают описывать ограничения в виде фактов и правил.</p><p>В функциональных языках (другой вариант декларативного программирование) описывают программу в виде функций. Отличие от функций в императивном программировании заключается в том, что функции в функциональном языке являются математическими в том смысле, что они устанавливают отношение между аргументом и результатом, и не могут изменять никаких переменных во время вычислений.</p><p>Вообще в декларативных языках обычно отсутствует изменение переменных или обычно спрятано за каким-либо специальным механизмом.</p><p>Самый популярный язык РСУБД SQL так же является декларативным. На нём описывается конечный результат, а способ его получения генерируется сервером СУБД исходя из множества факторов.</p><p>Декларативное программирование может являться более сложным в понимании, но позволяет писать более безопасный и поддерживаемый код, который легко параллелится. А компиляторы декларативных языков имеют больше возможностей при оптимизации программ.</p><p>Конечно, практически все основные языки сочетают в себе элементы и декларативного и императивного программирования. Огромное влияние оказывает функциональное программирование на JavaScript, Java, C++, C# и т.д.</p><p>Декларативное программирование — это парадигма программирования, в которой задаётся спецификация решения задачи: описывается, что представляет собой проблема и ожидаемый результат, но без описания способа достижения этого результата. Зачастую декларативные программы не используют понятия состояния и, в частности, не содержат переменных и операторов присваивания, обеспечивая ссылочную прозрачность. К подвидам декларативного программирования часто относят и функциональное программирование. Декларативные компьютерные языки часто не полны по Тьюрингу, так как теоретически не всегда возможно порождение исполняемого кода по декларативному описанию.</p><p>Императивное программирование — это парадигма программирования, в которой задаётся последовательность действий, необходимых для получения результата. В нём используются переменные, операторы присваивания и составные выражения.</p><p>Несмотря на то, что исторически первым был применен декларативный подход в программировании, первые языки программирования компьютеров (машинный, ассемблер, фортран, алгол, кобол) были императивными в силу простоты подхода.</p><p>Сейчас широко распространены как узкоспециализированные декларативные языки программирования (HTML + CSS, SVG, VRML, SQL, lex/VACC), в том числе функциональные (Haskell, Erlang, Scala), так и императивные языки (C/C++/C#, Java, Go, Rust, Python). Однако практически все современные языки программирования общего назначения высокого уровня, за исключением некоторых функциональных, относятся к императивным языкам.</p><p>Выбор той или иной парадигмы программирования — императивной или функциональной — определяется, главным образом, требованиями к программе и набором достоинств и недостатков каждой из парадигм. Так, например, независимость функций по данным в функциональных языках и отсутствие побочных эффектов чрезвычайно сокращает количество ошибок и позволяет эффективно распараллеливать код. Поэтому для создания высоконагруженных систем с высоким уровнем параллельных вычислений более оправданно выбирать один из функциональных языков.</p><p>С другой стороны, неизменность входных данных в функциональных языках программирования затрудняет создание систем, активно выполняющих ввод-вывод и модификацию уже имеющихся данных. Для реализации таких систем предпочтительнее выбирать императивные языки.</p><p>Императивное программирование — это парадигма, основанная на составлении алгоритма действий (инструкций/команд), которые изменяют состояние (информацию/данные/память) программы. Первыми языками программирования, основанными на таком подходе, были машинные коды и ассемблеры. Фактически, программа на этих языках — это код, который выполняется компьютером сразу, без предварительной компиляции. Из языков высокого уровня, требующих компиляции исходного кода программы в машинный код (или интерпретации), к императивным можно отнести C, C++, Java.</p><p>Декларативное программирование — это парадигма, при которой описывается желаемый результат, без составления детального алгоритма его получения. В пример можно привести HTML и SQL. При создании HTML мы с помощью тегов описываем, какую хотим получить страничку в браузере, а не то, как нарисовать на экране заголовок статьи, оглавление и текст. В SQL, если нам нужно посчитать количество сотрудников с фамилией «Сидоров», мы напишем SELECT count(*) FROM employee WHERE last_name = 'Сидоров';. Тут ничего не сказано про то, в каком файле или области памяти находятся данные по сотрудникам, как именно выбрать из них всех Сидоровых и нужно ли вообще это делать для подсчёта их количества.</p><p>Рассмотрим ещё один пример. Допустим, мы хотим приготовить обед.</p><p>В императивной парадигме это выглядит как-то так:</p><ul><li>купить мясо, огурцы, помидоры, соль;</li><li>порезать мясо, посолить;</li><li>поставить сковородку на плиту;</li><li>…</li></ul><p>В декларативной: хочу на обед жареное мясо с овощами (неплохо звучит, правда? :)).</p><p>Вроде бы различия очевидны. Однако, императивный язык не мешает обобщить и автоматизировать отдельные задачи. Можно реализовать некий «слой» кода, библиотеки, которые будут «уметь» выполнять отдельные этапы алгоритма: определять по рецепту, есть ли в наличии необходимые продукты, заказывать их доставку, пользоваться плитой и т.д. Получится, что программный код императивного языка программирования, использующий такие библиотеки, уже не будет по своей структуре так уж сильно отличаться от декларативного.</p><p>На практике, при написании кода и выбора подхода, разработчик отталкивается не только от возможностей и ограничений языка программирования, но и удобства использования той или иной парадигмы в данном конкретном случае.</p><h3>Что из себя представляют императивное и декларативное программирование?</h3><p>Если кратко, то императивная программа содержит прямые указания, что должен сделать компьютер и в каком порядке должны выполняться инструкции. Примерами императивных языков являются Java, Python, JavaScript, C, C++.Декларативная же программа состоит из ограничений и правил, из которых компьютер генерирует способ получения результата. Пример декларативного языка: SQL.</p><p>Напоминаем, что вы можете <a href="https://docs.google.com/forms/d/e/1FAIpQLSdSanNvlfPRrSyQWfnoGPflSVwO4KctnjOdEKHzuxjCmFX2dA/viewform">задать свой вопрос</a> экспертам, а мы соберём на него ответы, если он окажется интересным. Вопросы, которые уже задавались, можно найти в списке выпусков <a href="https://tproger.ru/experts/">рубрики</a>. Если вы хотите присоединиться к числу экспертов и прислать ответ от вашей компании или лично от вас, то пишите на <a>experts@tproger.ru</a>, мы расскажем, как это сделать.</p>]]></content:encoded>
    </item>
    <item>
      <title>Что такое динамическое программирование — объясняют эксперты</title>
      <link>https://tproger.ru/experts/what-is-dynamic-programming</link>
      <comments>https://tproger.ru/experts/what-is-dynamic-programming?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Никита Прияцелюк]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/experts/what-is-dynamic-programming</guid>
      <description><![CDATA[<p>Начинающий программист может спутать динамическое программирование с объектно-ориентированным. Эксперты простым языком объясняют суть подхода.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/experts/what-is-dynamic-programming">Что такое динамическое программирование — объясняют эксперты</a>»</p>]]></description>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Основные принципы программирования]]></category>
      <category><![CDATA[Ответы экспертов]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 23 Sep 2020 11:21:43 GMT</pubDate>
      <content:encoded><![CDATA[<p>Время от времени в разных статьях упоминается динамическое программирование, которое начинающий программист может спутать с чем-нибудь вроде объектно-ориентированного программирования. Мы попросили экспертов простым языком объяснить, что такое динамическое программирование.</p><p>Что такое динамическое программирование?</p><p>Динамическое программирование — достаточно широко используемый подход к решению задач, основанный на идее «разделяй и властвуй». Достаточно часто невозможно или крайне затруднительно создать некий общий алгоритм решения проблемы, который бы брал входные данные, что-то с ними делал и сразу выдавал ответ.</p><p>Часто способа решить задачу методом «делай раз, делай два, делай три» не существует. Но всегда можно разделить большую задачу на множество маленьких и дробить так до тех пор, пока упрощённые задачи не станут примитивно простыми. Это подход, известный как «разделяй и властвуй». Довольно часто для этого используется рекурсия — вызов функцией самой себя.</p><p>Важная особенность чтобы этот процесс не зациклился нужно чтобы на каком-то этапе задача сводилась к примитивному случаю, ответ на который известен сразу. Пример — вычисление факториала числа. Такую задачу можно решить и с помощью простого алгоритма n!=1·2·3·…·n, в этом случае можно сразу получить значение (например 4! = 1·2·3·4 = 24, а 5! = 1·2·3·4·5 = 120). Но эту же задачу можно решить и рекурсивно, ведь 5! = 5·4!, а 4! = 4·3! и так далее. Значит можно написать простую функцию расчета факториала n!, которая будет умножать число на результат вызова самой себя для более простого случая (n-1)!. И только в примитивном случае 1! функция вызывать саму себя не будет, а сразу вернёт 1, это важный момент, чтобы не возникло зацикливания.</p><p>Но «разделяй и властвуй» — это ещё не совсем динамическое программирование, так как такой подход хоть и прост, но часто приводит к лавинообразному росту необходимых вычислений, хоть сам метод может быть сколь угодно простым.</p><p>Взять, к примеру, задачу поиска кратчайшего маршрута по городу из точки А в точку Б. На практике такие задачи решаются с использованием теории графов, когда каждой улице в городе ставится в соответствие ребро графа, а каждой возможной точке пребывания — узел графа. Каждому ребру приписывается некоторая условная «стоимость», соответствующая, например времени прохождения или даже непосредственно денежная стоимости проезда по соответствующей «улице». Эта стоимость в реальности может даже меняться с течением времени, например из-за пробок. Тогда необходима функция, находящая кратчайший маршрут — такую последовательность узлов, пройдя через которые мы получим минимальную «стоимость» прохождения всех соединяющих их рёбер.</p><p>И вот для таких задач методы динамического программирования становится практически единственными рабочими вариантами решения. Методы рекурсии здесь уже не подойдут: можно, конечно, решить эту задачу и таким образом: написать функцию, возвращающую стоимость ухода из точки А по каждой из возможных «улиц», каждая из этих функций в свою очередь добавит к ней стоимость участка до следующего «перекрёстка», вызвав саму себя еще раз, и повторит это многократно, пока в результате не «дойдёт» до точки Б. По итогу, среди всех цепочек ответов можно будет выбрать ту, стоимость которой минимальна и получить ответ на задачу. Но на практике сделать такое возможно только для очень простых графов с десятками узлов, например для карты метро. А вот уже в масштабах карты улиц города подобные алгоритмы становятся нереалистичными.</p><p>Если вкратце, то динамическое программирование — это способ решить задачу, разбив её на мелкие задачи и скомбинировав их решения.</p><p>Обычно динамическое программирование применяют в задачах, связанных с оптимизацией, например, когда нужно найти кратчайший маршрут для перемещения из города A в город B. Либо это могут быть задачи, где нужно просчитать все возможные комбинации переходов или расположения элементов. Классический пример, в котором используется этот метод — последовательность чисел Фибоначчи.</p><p>F(n) = F(n-1) + F(n-2);<br />F(0)=0;<br />F(1)=F(2)=1;</p><p>Как видно, для решения задачек с последовательностями, нужно определить следующие условия: рекуррентная зависимость элементов последовательности друг от друга, начальное состояние. Они не всегда могут быть заданы в условии напрямую, как это было в задаче выше. Например, в видео «<a href="https://youtu.be/UVAfng6OCW0">Динамическое программирование: траектории кузнечика</a>» разбирают задание с количеством переходов из точки A в B.</p><p>В рассмотренных выше случаях параметром состояния было одно число, однако существуют и более сложные, многомерные классы задач. Для многомерной динамики параметрами состояния могут быть:</p><ul><li>массивы,</li><li>векторы,</li><li>отрезки внутри массивов,</li><li>деревья,</li><li>подмножества,</li><li>динамика по профилю</li><li>и т. д.</li></ul><p>Поиск оптимального состояния динамики, переходов и порядка пересчёта (прямой или обратный) и включает в себя метод динамического программирования.</p><p>Узнать подробности о применениях этого метода и способах его оптимизации можно почти во всех в книжках по алгоритмам, например, в книге «Алгоритмы. Построение и анализ».</p><p>В теории вычислительных систем под термином «динамическое программирование» понимается способ решения сложных задач путём разбиения их на более простые подзадачи. Динамическое программирование применимо не ко всем задачам, а лишь к тем, которые обладают «оптимальной структурой». Оптимальная структура означает, что задача может быть разбита на несколько аналогичных задач меньшего размера, при этом для решения конечной задачи могут быть использованы результаты решения меньших задач. Примеры задач с оптимальной структурой — вычисление факториала числа, построение ряда чисел Фибоначчи, вычисление расстояния Левенштейна (красивое название, скрывающееся внутри всем известной задачи diff, вычисляющей разницу между двумя текстовыми файлами). Программист, не знакомый с теорией вычислительных систем, может подумать, здесь кроется какая-то сложная математика, но на самом деле речь идёт о всем хорошо знакомом подходе — рекурсии. Все знают, как вычислить факториал числа: нужно умножить это число на факториал числа, меньшего на единицу: F(n) = n*F(n-1) (для краткости опущена проверка n &gt;= 1) — просто и изящно.</p><p>Так вот, динамическое программирование — это рекурсия с сохранением результатов вычислений. Представим, что нам постоянно нужно вычислять факториал разных чисел. Каждый раз, когда нам нужно вычислить факториал, например, числа 5, нам необходимо произвести 4 умножения — 5*4*3*2*1. А что, если сохранить промежуточные результаты? Допустим, мы когда-то раньше вычислили 4! = 24. Значит, нам нужно проделать только одно умножение — 5*24. А в следующий раз, когда нас спросят 5!, мы вообще можем вернуть результат сразу же. Понятно, что при таком подходе растёт скорость вычислений, но и возрастает потребление памяти. Что важнее — скорость или память — вечный вопрос. Принимать решение нужно исходя из конкретных условий.</p><p>Динамическое программирование – это метод, который позволяет эффективно решать многие задачи, прежде всего, задачи комбинаторной оптимизации.</p><p>Суть метода в следующем: имеющуюся задачу рекурсивно разбиваем на более маленькие подзадачи, их — на ещё меньшие и так далее. Но решаем задачи в обратном порядке: сначала маленькие (запоминаем их решение), потом переходим к задачам побольше (строим их решение на основе сохранённых решений маленьких задач) и так далее, пока не решим исходную большую задачу.</p><p>Преимущества метода:</p><ul><li>за счёт запоминания решения промежуточных подзадач мы каждую такую подзадачу решаем только один раз, т.е. экономим время;</li><li>избавляемся от рекурсии (заменяем её на вложенные циклы).</li></ul><p>Недостаток: обычно требуется много памяти для хранения промежуточных результатов.</p><p>Вычислительная сложность многих задач не зависит от входных параметров, например, получение элемента по индексу в массиве не зависит от длины массива и всегда выполняется за константное время. В то же время есть задачи, для которых время выполнения линейно зависит от значения параметров, например, получение элемента по индексу в связанном списке ― чем больше элементов, тем больше времени потребуется на поиск нужного элемента. Естественно, существуют и задачи, для которых использование полного перебора требует слишком много времени, например, поиск кратчайшего маршрута между двумя городами в случае полного перебора потребует построение n! вариантов, что при больших n неэффективно.</p><p>Некоторые подобные задачи можно решить путем разбиения исходной задачи на подзадачи меньшего размера и сложности, решив которые и объединив результаты, можно получить решение исходной задачи. Такой подход называется динамическим программированием и был предложен Ричардом Беллманом в 1940-х годах.</p><p>Динамическое программирование придерживается двух подходов к решению задач:</p><ul><li>Нисходящее динамическое программирование, когда исходная задача разбивается на подзадачи. После решения подзадач результаты объединяются для получения решения исходной задачи.</li><li>Восходящее динамическое программирование, когда сначала происходит решение подзадач, а затем объединение их результатов для решения общей задачи.</li></ul><p>Одной из самых известных задач динамического программирования является задача вычисления чисел Фибоначчи, и она эффективно решается методом восходящего динамического программирования. При использовании этого подхода для нахождения N-го элемента в последовательности происходит последовательное нахождение первого, второго и последующих элементов как суммы двух предыдущих. Данный подход имеет сложность O(N), в то время как использование классического рекурсивного подхода имеет приблизительную сложность O(2^N), что существенно больше.</p><p>С помощью чисел Фибоначчи описываются многие явления, однако, давайте посмотрим на более практический пример ― задачу коммивояжера или, на современный лад, курьера ― курьер должен посетить N адресов, побывав на каждом из адресов ровно по одному разу и завершить свой маршрут в исходной точке. Задача заключается в нахождении маршрута минимальной длинны.</p><p>Данную задачу можно решить методом полного перебора ― сгенерировать все возможные маршруты (всего их N!) для N адресов, а затем выбрать маршрут минимальной длины. Сложность данного алгоритма O(N! * N) и время вычисления очень быстро растет при росте количества адресов ― если для трех адресов нужно рассмотреть шесть вариантов, то для 10 уже около четырех миллионов!</p><p>Использование подходов динамического программирования может не дать наилучшего решения, но, тем не менее, оно будет достаточно хорошим и за приемлемое время. Суть подхода к решению данной задачи заключается в поиске ближайшего адреса на каждом шаге ― из исходной точки, затем следующего ближайшего пункта назначения из первого адреса и так далее. Полученное решение не будет идеальным, но потребует гораздо меньше времени ― O(N^2 * 2^N), что для тех же 10 адресов 1124 вычислений.</p><p>Таким образом, мы рассмотрели основную идею динамического программирования ― разбиение сложной задачи на коллекцию более простых, которые суммарно можно решить за гораздо меньшее время.</p><p>Чаще всего под динамическим программированием понимают запоминание результатов решения частных задач для того, чтобы затем переиспользовать их для решения более общих<br />задач.</p><p>Так, если у нас есть задача, которую можно поделить на набор похожих подзадач с более простым решением, ее нужно делить столько раз, сколько это возможно. Когда подзадачи перекрываются, это ускоряет решение.</p><p>Яркий пример того, как работает динамического программирование — задача нахождения N-го члена рекуррентной последовательности. В такой последовательности каждый следующий член зависит от одного или нескольких предыдущих. При этом, полностью раскрывая последовательность, мы «снижаем индекс», пока не дойдем до тривиального случая, либо до случая, на который уже есть сохраненный ответ — от него мы возвращаемся обратно и производим подсчет, сохраняя вычисленный результат для дальнейшего переиспользования. Если необходимо подсчитать большое количество членов последовательности, мы получим качественный скачок по производительности.</p><p>Данный метод применяют, чтобы ускорить и упростить решение задачи.</p><h3>Что такое динамическое программирование?</h3><p>Если кратко, то динамическое программирование — это способ решения сложных задач путём их разбиения на более мелкие составляющие и комбинирования их решений. Классический пример, в котором используется этот метод — последовательность чисел Фибоначчи, где каждое последующее число вычисляется из двух предыдущих.Динамическое программирование применимо не ко всем задачам, а только к тем, которые позволяют себя разбить на несколько задач поменьше и использовать их решения.</p><p>Напоминаем, что вы можете <a href="https://docs.google.com/forms/d/e/1FAIpQLSdSanNvlfPRrSyQWfnoGPflSVwO4KctnjOdEKHzuxjCmFX2dA/viewform">задать свой вопрос</a> экспертам, а мы соберём на него ответы, если он окажется интересным. Вопросы, которые уже задавались, можно найти в списке выпусков <a href="https://tproger.ru/experts/">рубрики</a>. Если вы хотите присоединиться к числу экспертов и прислать ответ от вашей компании или лично от вас, то пишите на <a>experts@tproger.ru</a>, мы расскажем, как это сделать.</p>]]></content:encoded>
    </item>
    <item>
      <title>Качественная архитектура ПО на примере концепции Linux «всё есть файл»</title>
      <link>https://tproger.ru/translations/good-software-code-architecture</link>
      <comments>https://tproger.ru/translations/good-software-code-architecture?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Сергей Штукатуров]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/good-software-code-architecture</guid>
      <description><![CDATA[<p>Концепция Linux «всё — файл» помогает создавать качественную архитектуру. На примере FFmpeg показана организация кодовой базы на C с расширяемостью.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/good-software-code-architecture">Качественная архитектура ПО на примере концепции Linux «всё есть файл»</a>»</p>]]></description>
      <category><![CDATA[Архитектура ПО]]></category>
      <category><![CDATA[Основные принципы программирования]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 03 Oct 2019 14:31:01 GMT</pubDate>
      <content:encoded><![CDATA[<p>В статье показано, как в Linux/FFmpeg организована кодовая база на C с учётом расширяемости, которая работает так, будто в C есть полиморфизм. Вы увидите, как концепция Linux «всё — файл» работает на уровне исходного кода, а также как FFmpeg позволяет быстро и легко добавлять поддержку новых форматов и кодеков.</p><figure><img src="https://media.tproger.ru/uploads/2019/10/diagram_components.jpg" alt="" /></figure><h2>Качественный дизайн ПО — введение</h2><p>В процессе работы над кодом программисты <a href="https://martinfowler.com/articles/is-quality-worth-cost.html">регулярно сталкиваются</a> с тем, что качественный дизайн кода <a href="https://www.martinfowler.com/bliki/Yagni.html">окупается впоследствии</a> при усложнении продукта. Для создания полезного и легко поддерживаемого в долгосрочной перспективе ПО разработчики подбирают определённые шаблоны и объединяют их в абстракции, и похоже, что разработчики Linux и FFmpeg поступили именно так.</p><p>При разработке программ создаются структуры данных и определяются их зависимости и поведение. То, как они построены и связаны между собой, можно рассматривать как дизайн/архитектуру ПО.</p><p>Предположим, что мы разрабатываем фреймворк для обработки видео- и аудиофайлов. Кодеки <a href="https://aomedia.org/">AV1</a>, H264, HEVC и AAC производят некоторые идентичные операции с данными, и если мы разработаем некоторую обобщённую абстракцию, включающую эти операции, мы сможем использовать эту абстракцию вместо того, чтобы реализовывать конкретную идею, заложенную в каждом отдельном кодеке.</p><p>Ещё один хороший приём — использовать слабо связанные компоненты, чётко определив их функции.</p><h2>Ruby</h2><p>Возможно, эти концепции проще понять на практике. Сделаем примерный набросок фреймворка для обработки потоковых медиаданных, использующего несколько разных кодеков.</p><p>Этот код на <a href="https://www.ruby-lang.org/en/">Ruby</a> отражает одну из описанных выше концепций. Без конкретизации в коде предполагается, что каждый кодек реализует функции encode и decode. Поскольку Ruby — язык с динамической типизацией, любой класс может иметь реализацию этих двух операций и работать как кодек.</p><p>Такой дизайн кода можно назвать хорошим, поскольку если нам потребуется добавить новый кодек, нужно только включить его реализацию в список. Разумеется, список можно сделать и динамическим. Смысл примера в том, что такой код легко расширять и поддерживать, поскольку компоненты слабо связаны между собой и каждый из них делает только то, что должен.</p><p>Фреймворк Ruby on Rails подталкивает к определённым способам организации кода, реализуя архитектуру «Модель-Представление-Контроллер» (MVC).</p><h2>Go</h2><p>Обращаясь к языкам со статической типизацией, таким как <a href="https://golang.org/">Go</a>, нам придётся быть более формальными, описывая требуемые типы, но мы всё равно можем создать код, аналогичный приведённому выше.</p><p>Тип <a href="https://medium.com/golangspec/interfaces-in-go-part-i-4ae53a97479c">interface в Go</a> намного мощнее аналогичной конструкции в Java, так как его определение никак не связано с реализацией, и наоборот. Можно даже присвоить каждому кодеку тип <a href="https://golang.org/pkg/io/#ReadWriter">ReadWriter</a> и использовать в таком виде.</p><h2>С</h2><p>На C тоже можно создать код с аналогичным поведением, но будут некоторые отличия.</p><p>Примечание Код создан по примеру <a href="https://www.bottomupcs.com/abstration.xhtml">размещённого</a> на сайте <a href="https://www.bottomupcs.com/">Computer Science from the Bottom Up</a>.</p><p>Сначала в обобщённой структуре мы определяем абстрактные операции (в данном случае функции). Затем мы наполняем их конкретным кодом, например кодером и декодером кодека av1.</p><p>Множество других языков поддерживают <a href="https://clojure.org/reference/protocols">сходные механизмы</a> распределения методов или функций, как если бы они придерживались некой конвенции. В результате ПО на уровне ОС достаточно уметь работать только с показанными высокоуровневыми абстракциями.</p><h2>Linux kernel и концепция «всё — файл»</h2><p>Концепция <a href="https://en.wikipedia.org/wiki/Everything_is_a_file">«всё — файл»</a> ОС Linux позволяет использовать один интерфейс для работы с любыми ресурсами системы. Например, Linux обрабатывает сетевые сокеты, особые файлы (такие как /proc/cpuinfo) и даже USB-устройства как файлы.</p><p>Этот подход облегчает разработку программ для ОС, поскольку мы можем использовать хорошо изученный набор операций для абстракции, названной «файлом». Вот как это работает:</p><p>Это возможно только потому, что концепция файла (структуры данных и операции) была разработана как один из главных способов взаимодействия подсистем.  Вот участок API-структуры file_operations:</p><p>Эта структура чётко определяет то, что мы подразумеваем под концепцией файла, и какое поведение мы от него ожидаем:</p><p>Здесь можно увидеть набор функций, реализующих это поведение, в <a href="https://github.com/torvalds/linux/blob/master/fs/ext4/dir.c#L653">файловой системе ext4</a>.</p><p>Даже файлы <a href="https://github.com/torvalds/linux/blob/master/fs/proc/cpuinfo.c#L19">cpuinfo proc</a> реализованы через эту абстракцию. Фактически, работая с файлами под Linux, вы используете виртуальную файловую систему (<a href="https://en.wikipedia.org/wiki/Virtual_file_system">VFS</a>), которая в свою очередь обращается к функциям абстракции.</p><figure><img src="https://media.tproger.ru/uploads/2019/10/pic2.jpg" alt="" /></figure><h2>FFmpeg — форматы</h2><p>Вот общая схема архитектуры <a href="https://github.com/leandromoreira/ffmpeg-libav-tutorial#ffmpeg-libav-architecture">процессов FFmpeg</a>, демонстрирующая, что внутренние компоненты связаны в основном через такие абстрактные концепции, как <a href="https://ffmpeg.org/doxygen/trunk/structAVCodec.html">AVCodec</a>, а не напрямую через конкретные кодеки.</p><figure><img src="https://media.tproger.ru/uploads/2019/09/pic3.png" alt="" /></figure><p>Для входящих файлов в FFmpeg создаётся структура <a href="https://github.com/FFmpeg/FFmpeg/blob/master/libavformat/avformat.h#L640">AVInputFormat</a>, реализуемая любым форматом (видеоконтейнером), который требуется использовать. Файлы MKV также <a href="https://github.com/FFmpeg/FFmpeg/blob/master/libavformat/matroskadec.c#L4229">заполняют эту структуру</a> своей реализацией, как и <a href="https://github.com/FFmpeg/FFmpeg/blob/master/libavformat/mov.c#L7997">формат MP4 — своей</a>.</p><p>Такой дизайн позволяет легко интегрировать новые кодеки, форматы и протоколы. В мае 2019 года в FFmpeg был включён кодек <a href="https://code.videolan.org/videolan/dav1d">DAV1d</a> (аналог av1 с открытым исходным кодом), и, <a href="https://git.ffmpeg.org/gitweb/ffmpeg.git/commitdiff/beaa350e24167f4ff31275a6114693f5ce7cd409">изучив изменения в коде</a>, вы увидите, насколько безболезненно прошло внедрение. В итоге ему только требуется зарегистрироваться в качестве доступного кодека и придерживаться списка общих операций.</p><p>Безотносительно используемого нами языка мы всегда можем как минимум попытаться создать код со <a href="https://en.wikipedia.org/wiki/Loose_coupling">слабой зависимостью</a> и <a href="https://en.wikipedia.org/wiki/Cohesion_(computer_science)#High_cohesion">высокой согласованностью</a>. Именно эти два основных свойства позволят вам писать ПО, которое легко расширять и поддерживать.</p>]]></content:encoded>
    </item>
    <item>
      <title>Конкурентность и параллелизм — разные вещи</title>
      <link>https://tproger.ru/explain/concurrency-vs-parallelism</link>
      <comments>https://tproger.ru/explain/concurrency-vs-parallelism?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгений Туренко]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/explain/concurrency-vs-parallelism</guid>
      <description><![CDATA[<p>Не все программисты знают, в чём разница между конкурентностью и параллелизмом. В этой статье мы на простом примере объясним, в чём же она заключается.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/explain/concurrency-vs-parallelism">Конкурентность и параллелизм — разные вещи</a>»</p>]]></description>
      <category><![CDATA[Обучение программированию]]></category>
      <category><![CDATA[Основные принципы программирования]]></category>
      <category><![CDATA[Коротко о главном]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 20 Feb 2018 18:13:31 GMT</pubDate>
      <content:encoded><![CDATA[<p>Может показаться, что нет никакой разницы между параллелизмом и конкурентностью, но это потому что вы не вникали в суть дела. Давайте попробуем понять, чем они отличаются.</p><p>Конкурентность — это выполнение задач за определённое время (например, есть 5 процессов и все они в сумме выполняются в течение 60 минут по очереди). Важная деталь заключается в том, что задачи необязательно выполняются одновременно, поэтому их можно разделить на более мелкие и чередующиеся.</p><figure><img src="https://media.tproger.ru/uploads/2018/02/Concurrency-vs-parallelism-pic-1.jpg" alt="" /></figure><p>Проведём аналогию: шеф-повар нарезает лук и иногда проверяет духовку. Ему нужно прекратить нарезать лук, чтобы подойти к духовке, а затем снова начать нарезать и повторять этот процесс до конца приготовления блюда.</p><p>Как вы заметили, конкурентность связана в большей степени с логистикой. Если бы её не было, то повар ждал бы пока приготовится мясо в духовке, чтобы начать нарезать лук.</p><p>Параллелизм — это выполнение задач в одно и то же время (например, есть 5 задач, каждая из них выполняется в течение 60 минут). Само название подразумевает, что они выполняются параллельно.</p><figure><img src="https://media.tproger.ru/uploads/2018/02/Concurrency-vs-parallelism-pic-2.jpg" alt="" /></figure><p>Вернёмся на кухню: теперь у нас есть два шеф-повара. Один следит за духовкой, а второй нарезает лук. Работа разделена, так как теперь на кухне трудятся два повара.</p><p>Параллелизм — подкласс конкурентности: перед выполнением нескольких одновременных задач вам нужно их сначала правильно организовать.</p><p>Что ещё можно почитать на эту тему:</p><ol><li><a href="https://www.braveclojure.com/concurrency/">Сакральное искусство параллельного и конкурентного программирования.</a></li><li><a href="https://wiki.haskell.org/Parallelism_vs._Concurrency">Параллелизм vs. Конкурентность.</a></li><li><a href="https://tproger.ru/translations/programming-concepts-concurrency/">Основные принципы программирования: конкурентность</a>.</li></ol>]]></content:encoded>
    </item>
    <item>
      <title>Что такое разработка: объяснение для новичков</title>
      <link>https://tproger.ru/translations/learn-to-write-some-simple-code</link>
      <comments>https://tproger.ru/translations/learn-to-write-some-simple-code?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгений Туренко]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/learn-to-write-some-simple-code</guid>
      <description><![CDATA[<p>Из статьи вы узнаете, что такое программирование, как войти в разработку с нуля и освоите темы для начинающих с примерами на JavaScript.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/learn-to-write-some-simple-code">Что такое разработка: объяснение для новичков</a>»</p>]]></description>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Обучение программированию]]></category>
      <category><![CDATA[Основные принципы программирования]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 15 Aug 2017 19:03:21 GMT</pubDate>
      <content:encoded><![CDATA[<p>Объясняем, что такое программирование и с чего начать, на примере JavaScript. Этот язык занимает лидирующие позиции в рейтингах популярности, а также поддерживает императивные, структурируемые, объектно-ориентированные и управляемые событиями парадигмы.</p><p>Примечание Вы читаете улучшенную версию некогда выпущенной нами статьи.</p><ol><li><a href="https://tproger.ru/#part1">Набор инструментов для программирования с нуля</a></li><li><a href="https://tproger.ru/#part2">Переменные</a></li><li><a href="https://tproger.ru/#part3">Объекты</a></li><li><a href="https://tproger.ru/#part4">Массивы</a></li><li><a href="https://tproger.ru/#part5">API</a></li><li><a href="https://tproger.ru/#part6">Функции</a></li><li><a href="https://tproger.ru/#part7">Логические ветви и сравнения</a></li><li><a href="https://tproger.ru/#part8">Циклы</a></li><li><a href="https://tproger.ru/#part9">null и undefined</a></li><li><a href="https://tproger.ru/#part10">Области применения</a></li><li><a href="https://tproger.ru/#part11">Комментирование кода</a></li><li><a href="https://tproger.ru/#part12">Заключение</a></li></ol><h2>Набор инструментов для программирования с нуля</h2><p>Инструментарий типичного программиста чаще всего состоит из следующих вещей:</p><ul><li>компьютер;</li><li>интернет (прежде всего он нужен для поиска всего неизученного и неизвестного в любом из известных поисковых сервисов);</li><li>редактор кода (или IDE — комплекс программных средств, используемый программистами для разработки программного обеспечения), который поможет упорядочить всё, что вы создаёте;</li><li>компилятор или интерпретатор. Это программа, которая читает ваш код и пытается найти в нём ошибки. Затем он собирает ваш код в единый пакет и передаёт компьютеру для выполнения;</li><li>наушники. Возможно, вас будут отвлекать внешние шумы, а наушники — один из простых способов оградить себя от шумов.</li></ul><p>Вы можете использовать бесплатное ПО в начале работы, такое как <a href="https://atom.io/">Atom</a> и <a href="https://notepad-plus-plus.org/">Notepad++</a>. Также можно попробовать <a href="https://www.sublimetext.com/">SublimeText</a>, однако этот редактор является платным.</p><h2>Переменные</h2><p>В начале программирования нужно освоить переменные — именованные области памяти для хранения данных. Есть две основные причины, по которым следует именовать данные:</p><ol><li>Вам зачастую не известно, что будет скрываться под переменной</li><li>Вам может быть сложно вспомнить, что значит та или иная переменная.</li></ol><p>Примечание В названиях переменных нельзя использовать апострофы.</p><p>Чтобы переменная существовала, её нужно объявить. Объявление переменных служит в роли свидетельства о «рождении» для данных. Давайте посмотрим на пример:</p><p>Ключевое слово varозначает переменную.</p><p>Переменной нужно присвоить значение:</p><p>Теперь вы можете использовать переменную, the_number_of_days_in_december вместо числа 31.</p><p>Следует помнить, что вы также можете объявлять переменные для данных, которые ещё не существуют. Например, вы можете объявитьvar donalds_birthdayи сообщить приложению, чтобы оно ожидало ввода значения. Всё, что будет введено пользователем для этой переменной, будет использоваться в тексте под именемdonalds_birthday</p><p>Переменной также можно объявить целую строку или фрагмент текста:</p><p>Теперь можно где угодно в коде написатьgreat_song_lyricsи компьютер поймёт, что речь идёт о 'pasito a pasito, suave suavecito'. Проверим это утверждение.</p><p>Откройте консоль в Google Chrome нажатием клавиши F12 (для владельцев Mac рекомендуется ознакомиться с этой <a href="http://ggnome.com/wiki/Using_The_Browser_Error_Console">статьёй</a>). Перед вами появится следующее окно:</p><figure><img src="https://media.tproger.ru/uploads/2017/08/pic-js-cons-1-1.png" alt="" /></figure><p>Убедитесь, что выбрана вкладка «Console», кликните на пустом поле рядом со знаком «больше» (&gt;), объявите переменную и присвойте ей значение, а затем нажмите клавишу ввода.</p><figure><img src="https://media.tproger.ru/uploads/2017/08/replace-1-1.png" alt="" /></figure><p>В консоли отображается сообщение undefined из-за того, что строка кода не вывела какие-либо данные, чего, собственно, делать и не должна. Теперь введите название своей переменной в строку и нажмите кнопку ввода.</p><figure><img src="https://media.tproger.ru/uploads/2017/08/replace-2-1.png" alt="" /></figure><p>Поздравляем! Консоль (которая также служит в роли интерпретатора) понимает вашу переменную.</p><h2>Объекты</h2><p>А что такое разработка без объектов? В JavaScript объекты объявляются почти так же, как и переменные:</p><p>Здесь мы объявили четыре переменные, и сможем найти их, поэтому мы сможем найти их, когда захотим узнать что-нибудь о human. Всё, что вы видите в фигурных скобках, разграничивается на две части: слева прописываются поля, а справа указываются свойства для них. Объект может иметь любые свойства до тех пор, пока они находятся внутри {фигурных скобок}.</p><p>Мы можем использовать объект humanи ссылаться на него точно так же, как и на любую другую переменную. Компьютер поймёт, что нужно сослаться на какое-либо определённое свойство, если мы его напишем через точку:</p><p>Каждый из элементов сверху может быть изменён и ссылаться на другое свойство. Например, давайте изменим имя (name):</p><h2>Массивы</h2><p>Чтобы начать программировать, нужно изучить и массивы — списки похожих фрагментов данных. В программировании на JavaScript массивы выглядят следующим образом:</p><p>Для массивов принято использовать [квадратные скобки]. В данном случае вы можете использовать tweets в любом месте вашего кода, ссылаясь на массив, который только что определили.</p><p>Примечание Счёт массивов начинается с нуля, а не с единицы.</p><p>Если вы хотите обратиться к какому-то отдельному элементу, то можно сделать следующее:</p><p>tweets[0]</p><p>tweets[1]</p><p>tweets[2]</p><p>Внутри квадратных скобок мы пишем индекс элемента, к которому хотим обратиться.</p><p>Каждое из приведённых выше выражений — переменная. Вы можете присвоить им какое-нибудь новое значение, чтобы поэкспериментировать:</p><p>Теперь tweets[2] будет ссылаться на новое значение.</p><p>Массивы могут содержать текст, числа, даты, объекты и даже другие массивы. Массивы также могут быть свойствами объектов.</p><p>Чтобы обратиться к message, вы можете написать следующее:</p><p>И компьютер поймёт, что вы имеете в виду ‘Blink twice if you need help’</p><h2>API</h2><p>API — набор свойств и методов (целевых фрагментов кода), которые имеют свои имена, как переменные.</p><p>Массивы JavaScript также имеют свой собственный API. Свойство length является частью этого API. Другая его часть — метод push, который добавляет элементы в конец массива:</p><p>Метод похож на свойство, потому что к нему можно получить доступ через точку. Он отличается от свойства тем, что после него нужно поставить (круглые скобки). В этих круглых скобках хранятся данные, которые мы хотим добавить в наш массив. Теперь в tweets четыре элемента. Это выглядит вот так:</p><h2>Функции</h2><p>Функция — это фрагмент кода, который что-то выполняет и имеет имя. Функции можно легко объявлять:</p><p>Объявление функции начинается с ключевого слова, затем ей даётся имя. return заставляет значение «выпрыгивать» из функции. Затем функция завершается (если вы напишете код после return, то он не будет выполнен). Таким образом, вы можете сделать следующее:</p><p>Мы объявляем переменную с именем the_loneliest_number. Часть нашей команды вызывает функцию giveMeOne() и так как функция говорит return 1, выводится единица. Наша переменная будет содержать цифру 1. Теперь попробуйте ввести в браузере первый блок с функцией, затем блок с переменной, а затем ввести the_loneliest_number и нажать кнопку ввода. Вы увидите единицу.</p><p>Функция может быть свойством объекта. Она может быть элементом массива. Она может возвращать число, дату, строку, объект, массив, другую функцию, массив функций и т. д.</p><p>Часть данных, которую мы добавляем в функции, называется аргументом. Объявление функции, которая может ожидать аргументы, выглядит так:</p><p>Оператор return складывает аргументы number1и number2, а затем выдаёт результат.</p><p>В JavaScript также можно писать математические выражения как на калькуляторах:</p><ul><li>+используется для сложения;</li><li>-используется для вычитания;</li><li>(круглые скобки) служат для принудительного выполнения порядка операций;</li><li>*используется для умножения;</li><li>% используется для получения остатка деления.</li></ul><p>Вы могли бы записать предыдущую функцию таким образом:</p><p>Эта функция делает всё то же, что и предыдущая, только в неё введена переменная в качестве посредника.</p><h2>Логические ветви и сравнения</h2><p>Предположим, мы пишем приложение, которое определяет, разрешено ли конкретному человеку войти в ночной клуб. Представим, что в JavaScript API есть метод, который получает возраст пользователя. Мы назовём его getUserAge(). Также предположим, что существуют два других метода: allowThemInTheNightClub() APIAPIи throwThemOutOnTheirButt(). Как мы можем помочь нашей программе решить, какой из этих двух методов нужно вызвать, исходя из возвращаемого значения первого метода?</p><p>Вы уже знаете, что делает первая строка. age (возраст) может варьироваться от 7 до 101. Теперь нам нужно определить больше значение age, чем 21 или нет.</p><p>Мы делаем это с помощью оператора if(если) — ключевого слова, похожего на метод. Аргумент, который он ожидает, представляет собой какое-то выражение (обычно сравнение). Сравнения принимают два значения и сравнивают их друг с другом. В результате чего выбирается одно из следующих ключевых слов: true— в случае соответствия правилу и false— в случае несоответствия. Это называется логическим выражением.</p><p>В JavaScript есть возможность 6 видов сравнения:<br />• === сравнивает значения. Если они одинаковы, то вы увидите true. Например, 6 === 6 было бы true;<br />• !== сравнивает значения на неравенство. Если они не равны, то вы увидите true. Например, 6 !== 3 было бы true;<br />• &gt; проверяет, больше ли значение слева. Если больше, то вы увидите true. Например, 6 &gt; 3 было бы true;<br />• &lt; проверяет, больше ли значение справа. Если больше, то вы увидите true. Например, 3 &lt; 6 было бы true;<br />• &gt;= проверяет, больше или равна правая часть левой. Если больше или равна, то вы увидите true. Например, выражения 6 &gt;= 6и6 &gt;= 5были бы true;<br />• &lt;= проверяет, меньше или равна правая часть левой. Если больше или равна, то вы увидите true. Например, выражения 6 &lt;= 6 и 6 &lt;= 7 были бы true.</p><p>Оператор if оценивает сравнение. Если выводится true, то код выполняется внутри блока сравнения. Если выводится false, код не выполняется и игнорируется.</p><p>Оператор if также может работать с оператором else(иначе). Он содержит в себе блок кода, который будет выполнен, если сравнение вернёт false.</p><h2>Циклы</h2><p>Но что такое циклы в программировании? Иногда при работе с массивом может понадобиться выполнить какой-то блок кода несколько раз подряд. В таких случаях следует использовать циклы. Простейшим видом цикла JavaScript является while (цикл выполняется, пока условие истинно):</p><p>Цикл while использует тот же синтаксис, что и оператор if: нём используются круглые скобки, вы проходите через сравнение и т. д. Но блок ifвыполняет код внутри только один раз, а блок whileповторяется раз за разом. Он выполняет условие до тех пор, пока оно не станет false. Если оно соответствует true, блок запускается снова и снова.</p><p>Сколько раз будет выполняться цикл? Что же, в первый раз он оценит сравнение и проверит меньше ли index(который равен 0), чем the_real_slim_shady[0]. Если сравнение выведет true, то будет запущен цикл, так как indexравен нулю. С этого момента цикл будет выполняться до тех пор, пока блок indexне будет равен 4, так как the_real_slim_shady[4] не существует.</p><h2>null и undefined</h2><p>Если вы объявите переменную и не присвоите ей значение, то она будет содержать специальное значение undefined. Null значит почти то же самое. Предлагаем почитать, чем они различаются, а также узнать про <a href="https://tproger.ru/translations/how-to-handle-undefined-in-javascript/">обработку undefined</a>.</p><h2>Области применения</h2><p>Если вы объявите переменную внутри функции, то вы не сможете ею воспользоваться где-нибудь за пределами функции. Пример:</p><p>В функции объявлена переменная wildIndiscretions. Мир за пределами функции не знает о существовании переменной. Даже оператор ifне может ничего сделать в этом случае. Оператор ifсработает только внутри функции, если поместить его перед return.</p><h2>Комментирование кода</h2><p>Не всегда бывает понятно, что выполняет тот или иной код. Поэтому всегда следует комментировать его. В JavaScript комментарии начинаются с //:</p><p>var age = 21{</p><p>// переменная равна 21.</p><p>// Если она не равна 21, пожалуйста, измените на 21.</p><p>// Что сделать: добавить больше переменных.</p><p>}</p><h2>Так что же такое программирование</h2><p>Программирование — это написание приложений, основанное на определённых правилах, некоторые их которых перечислены в нашей статье.</p><p>Теперь вы понимаете, что представляет собой разработка на базовом уровне. Желаем успехов на пути к освоению мастерства программирования. Если же вы понимаете, что JavaScript — не ваше, узнайте, <a href="https://tproger.ru/experts/16/">какой язык программирования подойдёт для новичка</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Основные принципы программирования: императивное и декларативное программирование</title>
      <link>https://tproger.ru/translations/imperative-declarative-programming-concepts</link>
      <comments>https://tproger.ru/translations/imperative-declarative-programming-concepts?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Иван Бирюков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/imperative-declarative-programming-concepts</guid>
      <description><![CDATA[<p>Тайлер МакГиннис объясняет разницу между двумя подходами через метафоры и примеры кода, понятные не только опытным программистам.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/imperative-declarative-programming-concepts">Основные принципы программирования: императивное и декларативное программирование</a>»</p>]]></description>
      <category><![CDATA[Обучение программированию]]></category>
      <category><![CDATA[Основные принципы программирования]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 26 Jan 2017 14:41:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Рассказывает Тайлер МакГиннис, Google Developer Expert</p><p>Вы наверняка слышали о таких понятиях, как императивное и декларативное программирование, и скорее всего гуглили определения. И поэтому вы наверняка видели что-то подобное: “Императивное программирование — это описание того, как ты делаешь что-то, а декларативное — того, что ты делаешь. Это объяснение отлично подходит тем, кто уже разобрался в этом вопросе — но не новичкам.</p><p>Самым сложным является тот факт, что разница между декларативным и императивным подходами часто понятна интуитивно, но её сложно задать определением. Я общался со многими программистами и пришёл к заключению, что лучшее объяснение — это сочетание метафор и примеров кода. Итак, начнём.</p><p>Допустим, вы поняли, что слишком много времени уделяли работе, и решили пригласить свою половинку на свидание. Вы пришли в ресторан, подошли к администратору и сказали…</p><p>Императивный подход (как): Я вижу, что тот угловой столик свободен. Мы пойдём туда и сядем там.</p><p>Декларативный подход (что): Столик для двоих, пожалуйста.</p><p>Императивный подход означает то, как вы займёте место. Вы должны перечислить все шаги этого процесса. Декларативный же подход заявляет, что вам нужен столик на двоих.</p><p>Больше метафор!</p><p>Я задам вам вопрос и хочу, чтобы вы придумали и императивный, и декларативный подход.</p><blockquote>“Я у Ашана. Как мне пройти до твоего дома?”</blockquote><p>Императивный ответ:</p><blockquote>“Пройди черезсеверный выход парковки и поверни налево. Сядь на автобус 678 и выйди на остановке “Улица Победы”. Поверни направо, как если бы ты шёл в Икею. Иди прямо и поверни направо у первого светофора. На следующем светофоре поверни налево. Номер моего дома — 134.”</blockquote><p>Декларативный ответ:</p><blockquote>Мой адрес: Энск, улица Победы, дом 134</blockquote><p>Неважно, как я попаду к твоему дому, важно, на какой машине я приеду. У неё будет или императивная механическая КПП, или декларативная автоматическая КПП. Достаточно метафор?</p><p>Прежде чем мы обратимся к коду, важно понять, что многие декларативные подходы имеют определённый слой императивных абстракций. Вернёмся к нашим примерам. Декларативное обращение к сотруднику ресторана подразумевает, что он знает все императивные шаги по предоставлению вам столика. Знание адреса подразумевает, что у вас есть GPS-навигатор, который знает императивные шаги по составлению маршрута. У автомобиля с автоматической КПП есть определённый слой абстракций над передключением передач.</p><p>Итак, я повторюсь: многие (если не все) декларативные подходы имеют слой неких императивных абстракций.</p><p>Теперь мы перейдём от приятных метафор к реальному коду. Сперва посмотрим, какие языки являются декларативными, а какие — императивными:</p><ul><li>Императивные: C, C++, Java.</li><li>Декларативные: SQL, HTML.</li><li>Смешанные (могут быть таковыми): JavaScript, C#, Python.</li></ul><p>Вот типичные примеры на SQL и HTML:</p><p>Достаточно взглянуть на них, чтобы понять, что происходит. Они декларативны, заявляя, что должно быть сделано, а не как. Вы описываете желаемый результат, не углубляясь в инструкции. Неважно, как будут выбраны пользователи из Мексики. Неважно, как браузер распарсит ваш article. Важно, что вы получите мексиканских пользователей и новый header и paragraph на сайте.</p><p>Пока неплохо. Давайте рассмотрим примеры на JavaScript.</p><p>Представьте, что вы на собеседовании. Откройте консоль и ответьте на следующие вопросы.</p><ol><li>Напишите функцию, называющуюся double, которая принимает массив чисел и возвращает новый массив, каждый элемент которого в два раза больше входного: double([1,2,3]) -&gt; [2,4,6].</li><li>Напишите функцию, называющуюся add, которая принимает массив и возвращает сумму всех его элементов: add([1,2,3]) -&gt; 6.</li><li>Используя jQuery (или чистый JavaScript), добавьте обработчик события click к элементу с id, равным btn. По нажатию переключите класс highlight и смените текст на Add Highlight или Remove Highlight, в зависимости от текущего состояния элемента.</li></ol><p>Давайте взглянем на самые распространённые подходы к решению этих задач, которые являются императивными.</p><p>Разобравшись, что общего у этих императивных примеров, мы поймём, что именно делает их императивными.</p><ol><li>Очевидно, что все они описывают, как решить проблему: мы явно указываем все шаги.</li><li>Это уже не так очевидно для тех, кто не привык думать декларативно, или даже функционально. В каждом примере происходит изменение какого-либо состояния. В первых двух примерах происходило изменение переменной results, а в третьей состояние было в самой DOM — и его мы тоже изменяли.</li><li>Это уже субъективно, но я считаю, что код выше нечитаем. Я не могу с первого взгляда понять, что происходит — вместо этого мне приходится читать код построчно.</li></ol><p>А теперь взглянем на декларативные примеры. Их цель — решить все проблемы, описанные выше. Поэтому каждый пример должен описывать, что происходит, быть неизменяемым и читаемым.</p><p>Гораздо лучше ?</p><p>Заметьте, что в первых двух примерах я использовал встроенные методы JavaScript, map и reduce. Как видите, декларативные решения вновь оказались абстракциями над императивными реализациями. Но нас не интересует, как реализованы эти методы. Мы также не изменяем состояния, да и читается этот код лучше.</p><p>Ну а третий? В нём я немного схитрил, использовав React — но обратите внимание, что все три императивные ошибки исправлены. React замечателен тем, что в нём вы можете создавать декларативные пользовательские интерфейсы. Смотря на компонент Btn, сразу понятно, как будет выглядеть интерфейс. Кроме того, состояния «живут» не в DOM, а в самом React-компоненте.</p><p>Ещё одним преимуществом является то, что декларативный код является контекстно-независимым. Это значит, что его можно использовать в любой программе без изменений.</p>]]></content:encoded>
    </item>
    <item>
      <title>Основные принципы программирования: функциональное программирование</title>
      <link>https://tproger.ru/translations/functional-programming-concepts</link>
      <comments>https://tproger.ru/translations/functional-programming-concepts?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Иван Бирюков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/functional-programming-concepts</guid>
      <description><![CDATA[<p>Знакомство с функциональной парадигмой для тех, кто начинал с ООП: чем она отличается от процедурного и прототипно-ориентированного подходов.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/functional-programming-concepts">Основные принципы программирования: функциональное программирование</a>»</p>]]></description>
      <category><![CDATA[Обучение программированию]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Основные принципы программирования]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 23 Jan 2017 10:43:04 GMT</pubDate>
      <content:encoded><![CDATA[<p>Рассказывает Аарон Краус</p><p>Если вы такой же разработчик, как и я, то наверняка сперва изучали парадигму ООП. Первым вашим яыком были Java или C++ — или, если вам повезло, Ruby, Python или C# — поэтому вы наверняка знаете, что такое классы, объекты, экземпляры и т.д. В чём вы точно не особо разбираетесь, так это в основах той странной парадигмы, называющейся функциональным программированием, которая существенно отличается не только от ООП, но и от процедурного, прототипно-ориентированного и других видов программирования.</p><p>Функциональное программирование становится популярным — и на то есть причины. Сама парадигма не нова: <a href="https://www.haskell.org/">Haskell</a>, пожалуй, является самым функциональным языком, а возник он в 90-ых. Такие языки, как Erlang, Scala, Clojure также попадают под определение функциональных. Одним из основных преимуществ функционального программирования является возможность написания программ, работающих конкурентно (если вы уже забыли, что это — освежите память прочтением <a href="https://tproger.ru/translations/programming-concepts-concurrency/">статьи о конкурентности</a>), причём без ошибок — то есть взаимные блокировки и потокобезопасность вас не побеспокоят.</p><p>У функционального программирования есть много преимуществ, но возможность максимального использования ресурсов процессора благодаря конкурентному поведению — это его главный плюс. Ниже мы рассмотрим основные принципы функционального программирования.</p><p>Вступление: Все эти принципы не обязательны (многие языки следуют им не полностью). Все они теоретические и нужны для наиболее точного определения функциональной парадигмы.</p><h3>1. Все функции — чистые</h3><p>Это правило безусловно является основным в функциональном программировании. Все функции являются чистыми, если они удовлетворяют двум условиям:</p><ol><li>Функция, вызываемая от одних и тех же аргументов, всегда возвращает одинаковое значение.</li><li>Во время выполнения функции не возникают <a href="https://ru.wikipedia.org/wiki/Побочный_эффект_(программирование)">побочные эффекты</a>.</li></ol><p>Первое правило понятно — если я вызываю функцию sum(2, 3), то ожидаю, что результат всегда будет равен 5. Как только вы вызываете функцию rand(), или обращаетесь к переменной, не определённой в функции, чистота функции нарушается, а это в функциональном программировании недопустимо.</p><p>Второе правило — никаких побочных эффектов — является более широким по своей природе. Побочный эффект — это изменение чего-то отличного от функции, которая исполняется в текущий момент. Изменение переменной вне функции, вывод в консоль, вызов исключения, чтение данных из файла — всё это примеры побочных эффектов, которые лишают функцию чистоты. Может показаться, что это серьёзное ограничение, но подумайте ещё раз. Если вы уверены, что вызов функции не изменит ничего “снаружи”, то вы можете использовать эту функцию в любом сценарии. Это открывает дорогу конкурентному программированию и многопоточным приложениям.</p><h3>2. Все функции — первого класса и высшего порядка</h3><p>Эта концепция — не особенность ФП (она используется в Javascript, PHP и других языках) — но его обязательное требование. На самом деле, на Википедии есть целая статья, посвящённая <a href="https://ru.wikipedia.org/wiki/Функции_первого_класса">функциям первого класса</a>. Для того, чтобы функция была первоклассной, у неё должна быть возможность быть объявленной в виде переменной. Это позволяет управлять функцией как обычным типом данных и в то же время исполнять её.</p><p><a href="https://ru.wikipedia.org/wiki/Функция_высшего_порядка">Функции высшего порядка</a> же определяются как функции, принимающие другую функцию как аргумент или возвращающие функцию. Типичными примерами таких функций являются <a href="https://en.wikipedia.org/wiki/Map_(higher-order_function)">map</a> и <a href="https://en.wikipedia.org/wiki/Filter_(higher-order_function)">filter</a>.</p><h3>3. Переменные неизменяемы</h3><p>Тут всё просто. В функциональном программировании вы не можете изменить переменную после её инициализации. Вы можете создавать новые, но не можете изменять существующие — и благодаря этому вы можете быть уверены, что никакая переменная не изменится.</p><h3>4. Относительная прозрачность функций</h3><p>Сложно дать корректное определение <a href="https://en.wikipedia.org/wiki/Referential_transparency">относительной прозрачности</a>. Самым точным я считаю такое: если вы можете заменить вызов функции на возвращаемое значение, и состояние при этом не изменится, то функция относительно прозрачна. Это, быть может, очевидно, но я приведу пример.</p><p>Пусть у нас есть Java-функция, которая складывает 3 и 5:</p><p>Очевидно, что любой вызов этой функции можно заменить на 8 — значит, функция относительно прозрачна. Вот пример непрозрачной функции:</p><p>Эта функция ничего не возвращает, но печатает текст, и при замене вызова функции на ничто состояние консоли будет другим — значит, функция не является относительно прозрачной.</p><h3>5. Функциональное программирование основано на лямбда-исчислении</h3><p>Функциональное программирование сильно опирается на математическую систему, называющуюся <a href="https://ru.wikipedia.org/wiki/Лямбда-исчисление">лямбда-исчислением</a>. Я не математик, поэтому я не буду углубляться в детали — но я хочу обратить внимание на два ключевых принципа лямбда-исчисления, которые формируют самое понятие функционального программирования:</p><ol><li>В лямбда-исчислении все функции могут быть анонимными, поскольку единственная значимая часть заголовка функции — это список аргументов.</li><li>При вызове все функции проходят процесс <a href="https://ru.wikipedia.org/wiki/Каррирование">каррирования</a>. Он заключается в следующем: если вызывается функция с несколькими аргументами, то сперва она будет выполнена лишь с первым аргументом и вернёт новую функцию, содержащую на 1 аргумент меньше, которая будет немедленно вызвана. Этот процесс рекурсивен и продолжается до тех пор, пока не будут применены все аргументы, возвращая финальный результат. Поскольку функции являются чистыми, это работает.</li></ol><p>Как я уже говорил, лямбда-исчисление на этом не заканчивается — но мы рассмотрели лишь ключевые аспекты, связанные с ФП. Теперь, в разговоре о функциональном программировании вы сможете блеснуть словечком “лямбда-исчисление”, и все подумают, что вы шарите 🙂</p><h3>Заключение</h3><p>Функциональное программирование серьёзно напрягает мозги — но это очень мощный подход, и я считаю, что его популярность будет только расти.</p><p>Если вы хотите узнать о функциональном программировании побольше, то советуем вам ознакомиться с примерами использования принципов ФП в JavaScript (<a href="https://tproger.ru/translations/functional-js-1/">часть 1</a>, <a href="https://tproger.ru/translations/functional-js-2/">часть 2</a>), а также с <a href="https://tproger.ru/translations/functional-c-immutability/">циклом статей</a>, посвящённым функциональному C#.</p>]]></content:encoded>
    </item>
    <item>
      <title>Основные принципы программирования: интроспекция и рефлексия</title>
      <link>https://tproger.ru/translations/programming-concepts-introspection-reflection</link>
      <comments>https://tproger.ru/translations/programming-concepts-introspection-reflection?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Иван Бирюков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/programming-concepts-introspection-reflection</guid>
      <description><![CDATA[<p>Интроспекция позволяет программе исследовать тип и свойства объекта во время выполнения; вместе с рефлексией она есть почти в каждом языке.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/programming-concepts-introspection-reflection">Основные принципы программирования: интроспекция и рефлексия</a>»</p>]]></description>
      <category><![CDATA[Языки программирования]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Основные принципы программирования]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sun, 15 Jan 2017 19:06:53 GMT</pubDate>
      <content:encoded><![CDATA[<p>Рассказывает Аарон Краус</p><p>Часто во время работы программы нам бывает нужна информация о данных — например, какой у них тип или являются ли они экземпляром класса (в ООП). Опираясь на эти знания, нам нужно проводить над данными некоторые операции, или даже изменять их — но необходимого вида данных у нас может и не быть! Если вы ничего не поняли, не расстраивайтесь — мы подробно во всём разберёмся. Всё, что я здесь описал — это иллюстрация целей двух возможностей, присутствующих почти в каждом современном языке программирования: интроспекции и рефлексии.</p><h3>Интроспекция</h3><p><a href="https://ru.wikipedia.org/wiki/Интроспекция_(программирование)">Интроспекция</a> — это способность программы исследовать тип или свойства объекта во время работы программы. Как мы уже упоминали, вы можете поинтересоваться, каков тип объекта, является ли он экземпляром класса. Некоторые языки даже позволяют узнать иерархию наследования объекта. Возможность интроспекции есть в таких языках, как Ruby, Java, PHP, Python, C++ и других. В целом, инстроспекция — это очень простое и очень мощное явление. Вот несколько примеров использования инстроспекции:</p><p>В Python самой распространённой формой интроспекции является использование метода dir для вывода списка атрибутов объекта:</p><p>В Ruby интроспекция очень полезна — в частности из-за того, как устроен сам язык. В нём всё является объектами — даже класс — и это приводит к интересным возможностям в плане наследования и рефлексии (об этом ниже). Если вы хотите узнать об этом больше, советую прочитать мини-цикл <a href="https://thesocietea.org/2015/08/metaprogramming-in-ruby-part-1/">Metaprogramming in Ruby</a>.</p><p>Прим. перев. Также не будет лишним прочитать <a href="https://tproger.ru/articles/ruby-self-analysis/">нашу статью</a>, посвящённую интроспекции в Ruby.</p><p>Вот несколько простых примеров интроспекции с использованием IRB (Interactive Ruby Shell):</p><p>Вы также можете узнать у объекта, экземпляром какого класса он является, и даже “сравнить” классы.</p><p>Однако интроспекция — это не рефлексия; рефлексия позволяет нам использовать ключевые принципы интроспекции и делать действительно мощные вещи с нашим кодом.</p><h3>Рефлексия</h3><p>Интроспекция позволяет вам изучать атрибуты объекта во время выполнения программы, а рефлексия — манипулировать ими. <a href="https://ru.wikipedia.org/wiki/Рефлексия_(программирование)">Рефлексия</a> — это способность компьютерной программы изучать и модифицировать свою структуру и поведение (значения, мета-данные, свойства и функции) во время выполнения. Простым языком: она позволяет вам вызывать методы объектов, создавать новые объекты, модифицировать их, даже не зная имён интерфейсов, полей, методов во время компиляции. Из-за такой природы рефлексии её труднее реализовать в статически типизированных языках, поскольку ошибки типизации возникают во время компиляции, а не исполнения программы (подробнее об этом <a href="https://tproger.ru/translations/programming-concepts-compilation-vs-interpretation/">здесь</a>). Тем не менее, она возможна, ведь такие языки, как Java, C# и другие допускают использование как интроспекции, так и рефлексии (но не C++, он позволяет использовать лишь интроспекцию).</p><p>По той же причине рефлексию проще реализовать в интерпретируемых языках, поскольку когда функции, объекты и другие структуры данных создаются и вызываются во время работы программы, используется какая-то система распределения памяти. Интерпретируемые языки обычно предоставляют такую систему по умолчанию, а для компилируемых понадобится дополнительный компилятор и интерпретатор, который следит за корректностью рефлексии.</p><p>Мне кажется, что мы сказали много об определении рефлексии, но смысла это пока несёт мало. Давайте взглянем на примеры кода ниже (с рефлексией и без), каждый из которых создаёт объект класса <b>Foo </b>и вызывает метод hello.</p><p>Этот список отнюдь не исчерпывает возможности рефлексии. Это очень мощный принцип, который к тому же является обычной практикой в <a href="https://ru.wikipedia.org/wiki/Метапрограммирование">метапрограммировании</a>. Тем не менее, при использовании рефлексии нужно быть очень внимательным. Хотя у неё и есть свои преимущества, код, использующий рефлексию, значительно менее читаем, он затрудняет отладку, а также открывает двери по-настоящему плохим вещами, например, инъекции кода через выражения eval.</p><h4>Eval-выражения</h4><p>Некоторые рефлективные языки предоставляют возможность использования <a href="https://en.wikipedia.org/wiki/Eval">eval-выражений</a> — выражений, которые распознают значение (обычно строку) как выражение. Такие утверждения — это самый мощный принцип рефлексии и даже метапрограммирования, но также и самый опасный, поскольку они представляют собой угрозу безопасности.</p><p>Рассмотрим следующий пример кода на Python, который принимает данные из стороннего источника в Сети (это одна из причин, по которой люди пользуются eval-выражениями):</p><p>Защита программы будет нарушена, если кто-то передаст в метод get_data() такую строку:</p><p>Для безопасного использования eval-утверждений нужно сильно ограничивать формат входных данных — и обычно это лишь занимает лишнее время.</p><h3>Заключение</h3><p>Интроспекция и рефлексия — это очень мощные инструменты современных языков, и их понимание может позволить вам писать по-настоящему крутой код. Ещё раз отметим: интроспекция — это изучение атрибутов объекта, а рефлексия — это манипуляция ими. Будьте внимательны при использовании рефлексии, поскольку она может сделать ваш код нечитаемым и уязвимым. Чем больше сила, тем больше и ответственность — вот девиз всего, что связано с метапрограммированием.</p>]]></content:encoded>
    </item>
    <item>
      <title>Основные принципы программирования: статическая и динамическая типизация</title>
      <link>https://tproger.ru/translations/programming-concepts-type-checking</link>
      <comments>https://tproger.ru/translations/programming-concepts-type-checking?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Иван Бирюков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/programming-concepts-type-checking</guid>
      <description><![CDATA[<p>Система типов — набор правил, присваивающих тип переменным, выражениям и функциям; статическая и динамическая проверка типов работают по-разному.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/programming-concepts-type-checking">Основные принципы программирования: статическая и динамическая типизация</a>»</p>]]></description>
      <category><![CDATA[Языки программирования]]></category>
      <category><![CDATA[Динамическая типизация]]></category>
      <category><![CDATA[Основные принципы программирования]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 13 Jan 2017 10:11:09 GMT</pubDate>
      <content:encoded><![CDATA[<p>Рассказывает Аарон Краус</p><p>Когда вы изучаете языки программирования, то в разговорах часто слышите фразы наподобие “статически типизированный” или “динамически типизированный”. Эти понятия описывают процесс проверки соответствия типов, и как статическая проверка типов, так и динамическая, относятся к разным системам типов. Система типов — это набор правил, которые присваивают свойство, называющееся “тип”, различным сущностям в программе: переменным, выражениям, функциям или модулями — с конечной целью уменьшения количества ошибок путём подтверждения того, что данные отображаются корректно.</p><p>Не волнуйтесь, я знаю, что это всё звучит запутанно, поэтому мы начнём с основ. Что такое “проверка соответствия типов” и что такое вообще тип?</p><h3>Тип</h3><p><a href="https://ru.wikipedia.org/wiki/Тип_данных">Тип</a>, также известный как тип данных, это вид классификации, отмечающий одних из различных видов данных. Я не люблю использовать слово “тип” в этом смысле, поэтому скажем так: тип описывает возможные значения структуры (например, переменной), её семантическое значение и способ хранения в памяти. Если это звучит непонятно, подумайте о целых, строках, числах с плавающей запятой и булевых величинах — это всё типы. Типы можно разбить на категории:</p><ul><li>Примитивные типы — они варьируются в зависимости от языка, но самые основные — это целые, числа с плавающей запятой, булевы величины и символы.</li><li>Сложные типы — они состоят из несколько примитивных типов, например, массив или запись (но не хэш). Все сложные типы считаются <a href="https://ru.wikipedia.org/wiki/Структура_данных">структурами данных</a>.</li><li>Абстрактные типы — типы, у которых нет конкретной реализации, такие как хэш, множество, очередь и стек.</li><li>Прочие типы — например, <a href="https://ru.wikipedia.org/wiki/Указатель_(тип_данных)">указатели</a> (тип, в значении которого хранится ссылка на другое место в памяти).</li></ul><p>В различных языках могут различаться примитивные типы и структуры данных, но принцип везде один. Тип просто определяет набор правил и протоколов, которым должен соответствовать элемент данных.</p><h3>Проверка соответствия типов</h3><p>Существование типов не имеет смысла без процесса подтверждения того, что эти типы имеют логический смысл и программа может быть корректно исполнена. Тут и приходит на помощь <a href="https://ru.wikipedia.org/wiki/Система_типов#.D0.9F.D1.80.D0.BE.D0.B2.D0.B5.D1.80.D0.BA.D0.B0_.D1.81.D0.BE.D0.B3.D0.BB.D0.B0.D1.81.D0.BE.D0.B2.D0.B0.D0.BD.D0.B8.D1.8F_.D1.82.D0.B8.D0.BF.D0.BE.D0.B2">проверка соответствия типов</a>. Это процесс подтверждения и применения ограничений типов, и он может проходить как во время компиляции (т.е. статически), так и во время выполнения (т.е. динамически). Проверка соответствия типов нужна, чтобы убедиться в <a href="https://ru.wikipedia.org/wiki/Типобезопасность">типобезопасности</a> программы, что сведёт количество ошибок, связанных с типами, к минимуму. Это такие ошибки, которые возникают, когда операция производится с тем типом, с каким она производиться не должна: например, когда целое число принимается за число с плавающей запятой, или когда происходит сложения строки и целого:</p><p>Хотя во многих языках и строки, и числа могут использовать оператор +, это зачастую приводит к ошибкам типа, поскольку это выражение обычно не работает с разными типами данных.</p><p>Для случая не типобезопасной программы единого алгоритма действий нет. Какие-то языки выдают ошибку типа, которая останавливает компиляцию или выполнение, а в каких-то есть встроенные обработчики таких ошибок (что позволяет разработчикам порой ошибаться при работе с типами). Вне зависимости от этого, процесс проверки типов — это необходимость.</p><p>Теперь, когда мы знаем, что такое типы и как работает проверка соответствия типов, рассмотрим два основных вида проверки: статическую и динамическую.</p><h3>Статическая проверка типов</h3><p>Язык обладает статической типизацией, если тип переменной известен во время компиляции, а не выполнения. Типичными примерами таких языков являются Ada, C, C++, C#, JADE, Java, Fortran, Haskell, ML, Pascal, и Scala.</p><p>Большим преимуществом статической проверки типов является тот факт, что большую часть ошибок типов можно отловить на ранней стадии разработки. Статическая типизация обычно приводит к более быстрому исполнению скомпилированного кода, потому что компилятор знает точные типы используемых данных и создаёт оптимизированный машинный код. Статическая проверка типов оценивает лишь информацию, доступную во время компиляции, а также может подтвердить, что проверенные условия соблюдаются для всех возможных вариантов исполнения программы, что избавляет от необходимости проверки перед каждым запуском программы. Без статической проверки типов даже 100%-ное покрытие тестами не всегда поможет выявить некоторые ошибки типизации.</p><h3>Динамическая проверка типов</h3><p>Динамическая проверка типов — это процесс подтверждения типобезопасности программы во время её выполнения. Типичными примерами динамически типизированных языков являются Groovy, JavaScript, Lisp, Lua, Objective-C, PHP, Prolog, Python, Ruby, Smalltalk и Tcl.</p><p>Большая часть типобезопасных языков в той или иной мере использует динамическую проверку типов, даже если основным инструментом является статическая. Так происходит из-за того, что многие свойства невозможно проверить статически. Предположим, что программа определяет два типа, A и B, где B — подтип A. Если программа пытается преобразовать тип A в тип B, т.е. произвести <a href="https://ru.wikipedia.org/wiki/Понижающее_приведение">понижающее приведение</a>, то эта операция будет одобрена лишь в том случае, когда значение на самом деле имеет тип B. Поэтому для подтверждения безопасности операции нужна динамическая проверка типов.</p><p>В отличие от статической проверки типов, динамическая может привести к прекращению выполнения программы из-за ошибок типизации. В некоторых языках этого может избежать (например, благодаря обработке ошибок или слабой типобезопасности). Для избежания подобных ситуации рекомендуется использовать <a href="https://tproger.ru/translations/unit-tests-purposes/">юнит-тесты</a>.</p><p>Код, прошедший динамическую проверку типов, в общем случае менее оптимизирован; кроме того, существует возможность ошибок выполнения и, как следствие, необходимость проверки перед каждым запуском. Тем не менее, динамическая типизация открывает дорогу другим, мощным техникам программирования, например, <a href="https://ru.wikipedia.org/wiki/Метапрограммирование">метапрограммированию</a>.</p><h3>Типичные заблуждения</h3><h4>Миф 1: статическая / динамическая типизация ==  сильная / слабая типизация</h4><p>Обычным заблуждение является мнение, что все статически типизированные языки являются сильно типизированными, а динамически типизированные — слабо типизированными. Это неверно, и вот почему.</p><p>Сильно типизированный язык — это такой язык, в котором переменные привязаны к конкретным типам данных, и который выдаст ошибку типизации в случае несовпадения ожидаемого и фактического типов — когда бы не проводилась проверка. Проще всего представить сильно типизированный язык как язык с высокой типобезопасностью. Например, в уже использованном выше куске кода сильно типизированный язык выдаст явную ошибку типизации, которая прервёт выполнение программы:</p><p>Мы часто ассоциируем статически типизированные языки, такие как Java и C#, с сильно типизированным (они такими и являются), поскольку тип данных задаётся явно при инициализации переменной — как в этом примере на Java:</p><p>Тем не менее, Ruby, Python и JavaScript (все они обладaют динамической типизацией) также являются сильно типизированными, хотя разработчику и не нужно указывать тип переменной при объявлении. Рассмотрим такой же пример, но написанный на Ruby:</p><p>Оба языка являются сильно типизированными, но используют разные методы проверки типов. Такие языки, как Ruby, Python и JavaScript не требуют явного определения типов из-за <a href="https://ru.wikipedia.org/wiki/Вывод_типов">вывода типов</a> — способности программно выводить нужный тип переменной в зависимости от её значения. Вывод типов — это отдельное свойство языка, и не относится к системам типов.</p><p>Слабо типизированный язык — это язык, в котором переменные не привязаны к конкретному типу данных; у них всё ещё есть тип, но ограничения типобезопасности гораздо слабее. Рассмотрим следующий пример кода на PHP:</p><p>Поскольку PHP обладает слабой типизацией, ошибки в этом коде нет. Аналогично предыдущему предположению, не все слабо типизированные языки являются динамически типизированными: PHP — это динамически типизированный язык, но вот C — тоже язык со слабой типизацией — воистину статически типизирован.</p><p>Миф разрушен.</p><p>Хотя статическая / динамическая  и сильная / слабая системы типов и являются разными, они обе связаны с типобезопасностью. Проще всего это выразить так: первая система говорит о том, когда проверяется типобезопасность, а вторая — как.</p><h4>Миф 2: статическая / динамическая типизация == компилируемые / интерпретируемые языки</h4><p>Будет верным сказать, что большинство статически типизированных языков обычно компилируются, а динамически типизированных — интерпретируются, но обобщить это утверждение нельзя, и тому есть простой пример.</p><p>Когда мы говорим о типизации языка, мы говорим о языке как о целом. Например, неважно, какую версию Java вы используете — она всегда будет статически типизированной. Это отличается от того случая, когда язык является компилируемым или интерпретируемым, поскольку в этом случае мы говорим о конкретной реализации языка. В теории, любой язык может быть как компилируемым, так и интерпретируемым. Самая популярная реализация языка Java использует компиляцию в байткод, который интерпретирует JVM — но есть и иные реализации этого языка, которые компилируются напрямую в машинный код или интерпретируются как есть.</p><p>Если это всё ещё непонятно, советую прочесть <a href="https://tproger.ru/translations/programming-concepts-compilation-vs-interpretation/">одну из предыдущих статей</a> этого цикла.</p><h3>Заключение</h3><p>Я знаю, что в этой статье было много информации — но я верю, что вы справились. Я бы хотел вынести информацию про сильную / слабую типизацию в отдельную статью, но это не такая важная тема; к тому же, нужно было показать, что этот вид типизации не имеет отношения к проверке типов.</p><p>Нет однозначного ответа на вопрос “какая типизация лучше?” — у каждой есть свои преимущества и недостатки. Некоторые языки — такие как Perl и C# — даже позволяют вам самостоятельно выбирать между статической и динамической системами проверки типов. Понимание этих систем позволит вам лучше понять природу возникающих ошибок, а также упростит борьбу с ними.</p>]]></content:encoded>
    </item>
    <item>
      <title>Основные принципы программирования: конкурентность</title>
      <link>https://tproger.ru/translations/programming-concepts-concurrency</link>
      <comments>https://tproger.ru/translations/programming-concepts-concurrency?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Иван Бирюков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/programming-concepts-concurrency</guid>
      <description><![CDATA[<p>Конкурентность — свойство систем, допускающее одновременное выполнение нескольких взаимодействующих процессов в пересекающихся промежутках.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/programming-concepts-concurrency">Основные принципы программирования: конкурентность</a>»</p>]]></description>
      <category><![CDATA[Многопоточность]]></category>
      <category><![CDATA[Обучение программированию]]></category>
      <category><![CDATA[Основные принципы программирования]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sat, 07 Jan 2017 14:56:31 GMT</pubDate>
      <content:encoded><![CDATA[<p>Рассказывает Аарон Краус</p><p>В третьей статье <a href="https://tproger.ru/tag/programming-concepts/">цикла “Принципы программирования”</a> мы поговорим о конкурентности (concurrency). <a href="https://en.wikipedia.org/wiki/Concurrency_(computer_science)">Конкурентность</a> — это свойство систем (программы, сети, компьютера и т.д.), допускающее одновременное выполнение нескольких вычислительных процессов, которые могут взаимодействовать друг с другом. Вычисления запускаются, проходят и завершаются в пересекающихся промежутках времени; они также могут происходить абсолютно одновременно (<a href="https://en.wikipedia.org/wiki/Parallel_computing">параллелизм</a>), но это не обязательно.</p><h3>Конкурентность в программировании</h3><p>Конкурентность реализована в логике программирования таким образом, что она явно устанавливает отдельные точки исполнения вычислений или процессов, называемые управляющими потоками. Они позволяют этим вычислениям избежать ожидания завершения всех остальных вычислений — как это происходит в случае последовательного программирования.</p><h3>Конкурентные и параллельные вычисления</h3><p>Хотя и считается, что конкурентные вычисления включают в себя параллельные, у них есть существенные отличия.</p><p>Параллельные вычисления используют более одного вычислительного ядра, поскольку все управляющие потоки работают одновременно и занимают весь рабочий цикл ядра на время исполнения — именно поэтому параллельное вычисление невозможно на одноядерном компьютере. В этом они отличаются от конкурентных вычислений, которые фокусируются на пересечениях жизненных циклов вычислений. Например, этапы выполнения процесса могут быть разбиты на <a href="https://ru.wikipedia.org/wiki/Разделение_времени">временные промежутки</a>, и если процесс не заканчивает своё существование до конца промежутка, он приостанавливается, предоставляя другому процессу возможность работать.</p><h3>Зачем нужно конкурентное программирование?</h3><p>Главным преимуществом этого подхода является максимально возможное использование ресурсов системы. В начале 2000-ых стало популярным использование многоядерных процессоров, пришедших на смену одноядерным, пускай и с очень мощным на то время ядром. Это в первую очередь позволяет оптимизировать время выполнения программы путём разделения нагрузки на ядра.  Я не буду углубляться в эту тему — принципы синхронизации процессов и потоков различны в разных ОС — но вы можете почитать об этом, например, <a href="https://en.wikipedia.org/wiki/Synchronization_%28computer_science%29">здесь</a>.</p><p>В современных языках программирования принцип конкурентности обычно реализован в виде процесса <a href="https://en.wikipedia.org/wiki/Thread_(computing)#Multithreading">многопоточности</a>. Многопоточность позволяет программе работать в нескольких потоках, предоставляя преимущества параллелизации (быстрое исполнение, эффективное использование ресурсов и т.д.), но она также сохраняет и её недостатки (о них ниже), поэтому некоторые языки используют механизм, названный <a href="https://ru.wikipedia.org/wiki/Global_Interpreter_Lock">Global Interpreter Lock</a> (GIL). Обычно его можно встретить в стандартных реализациях Python и Ruby (CPython и Ruby MRI соответственно), он предотвращает одновременное исполнение более чем одного потока — даже на многоядерных процессорах. Это похоже на недостаток, но GIL нужен для предотвращения <a href="https://en.wikipedia.org/wiki/Thread_safety">потоконебезопасных</a> активностей. Реализации с GIL обычно повышают скорость работы однопоточных программ и облегчают интеграцию с библиотеками C, но за это приходится платить возможностями многопоточности.</p><h3>Проблемы конкурентного программирования</h3><p>Поскольку идеей конкурентности является одновременное исполнение вычислений, может случиться так, что эти отдельные вычисления получат доступ к разделёнными между ними ресурсам и случайно изменят их (так называемое потоконебезопасное поведение). В таких случаях используются <a href="https://en.wikipedia.org/wiki/Arbiter_(electronics)">арбитры</a>, но такой тип активности может создать неопределённость и привести к <a href="https://ru.wikipedia.org/wiki/Взаимная_блокировка">взаимной блокировке</a> и <a href="https://en.wikipedia.org/wiki/Starvation_(computer_science)">голоданию</a>.</p><p>Всё это делает координацию конкурентных задач очень важной, поскольку даже в тех местах, над которыми у разработчика почти нет власти — например, в распределении памяти <a href="https://tproger.ru/translations/programming-concepts-1/">в стеке и куче</a> — может возникнуть неопределённость.</p><p>Прим. перев. Рекомендуем также почитать наши материалы по теме многопоточного программирования:</p><ul><li><a href="https://tproger.ru/problems/safe-threads-in-cpp/">Безопасность потоков в С++</a>;</li><li><a href="https://tproger.ru/translations/multithreaded-exception-handling/">Обработка исключений в многопоточных приложениях</a>;</li><li><a href="https://tproger.ru/problems/write-a-class-that-provides-a-lock-so-as-to-prevent-the-occurrence-of-dead-lock/">Задача с разбором решения: “Разработайте класс, обеспечивающий блокировку так, чтобы предотвратить возникновение мертвой блокировки”</a>.</li></ul><h3>Будущее конкурентного программирования</h3><p>Конкурентное программирование — это очень мощный инструмент, даже с учётом его недостатков. Несколько языков предоставляют феноменальную поддержку инструментов для использования этого принципа: больше всего это проявляется в API языка <a href="https://golang.org/">Go</a>, созданного в Google. Разработчики других языков, например, Python и Ruby, видят отрицательные стороны конкурентности и поэтому используют GIL по умолчанию. Так или иначе, если вы пишете приложение, которое использует принципы конкурентности, обязательно уделите особое внимание планированию, иначе вы рискуете повредить данные.</p>]]></content:encoded>
    </item>
    <item>
      <title>Основные принципы программирования: компилируемые и интерпретируемые языки</title>
      <link>https://tproger.ru/translations/programming-concepts-compilation-vs-interpretation</link>
      <comments>https://tproger.ru/translations/programming-concepts-compilation-vs-interpretation?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Иван Бирюков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/programming-concepts-compilation-vs-interpretation</guid>
      <description><![CDATA[<p>Аарон Краус объясняет, чем компиляция отличается от интерпретации и как эти механизмы лежат в основе работы всех языков программирования.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/programming-concepts-compilation-vs-interpretation">Основные принципы программирования: компилируемые и интерпретируемые языки</a>»</p>]]></description>
      <category><![CDATA[Языки программирования]]></category>
      <category><![CDATA[Обучение программированию]]></category>
      <category><![CDATA[Динамическая типизация]]></category>
      <category><![CDATA[Основные принципы программирования]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 03 Jan 2017 19:53:31 GMT</pubDate>
      <content:encoded><![CDATA[<p>Рассказывает Аарон Краус</p><p>Как и в <a href="https://tproger.ru/translations/programming-concepts-1/">предыдущей статье</a> этого цикла, я хочу обратить ваше внимание на ключевые принципы программирования, которые влияют на всё то, что мы делаем, но с которыми мы редко сталкиваемся напрямую и поэтому не до конца их понимаем. Тема сегодняшней статьи — компилируемые и интерпретируемые языки.</p><p>Будучи разработчиками, мы часто сталкиваемся с такими понятиями, как компилятор и интерпретатор, но я считаю, что многие не совсем понимают, что они означают. Между тем, компиляция и интерпретация — это основы работы всех языков программирования. Давайте взглянем на то, как на самом деле устроены эти понятия.</p><h3>Вступление</h3><p>Мы полагаемся на такие инструменты, как компиляция и интерпретация, чтобы преобразовать наш код в форму, понятную компьютеру. Код может быть исполнен нативно, в операционной системе после конвертации в машинный (путём компиляции) или же исполняться построчно другой программой, которая делает это вместо ОС (интерпретатор).</p><p>Компилируемый язык — это такой язык, что программа, будучи скомпилированной, содержит инструкции целевой машины; этот машинный код непонятен людям. Интерпретируемый же язык — это такой, в котором инструкции не исполняются целевой машиной, а считываются и исполняются другой программой (которая обычно написана на языке целевой машины). Как у компиляции, так и у интерпретации есть свои плюсы и минусы, и именно это мы и обсудим.</p><p>Прежде чем мы продолжим, стоит отметить, что многие языки программирования имеют как компилируемую, так и интерпретируемую версии, поэтому классифицировать их затруднительно. Тем не менее, чтобы не усложнять, в дальнейшем я буду разделять компилируемые и интерпретируемые языки.</p><h3>Компилируемые языки</h3><p>Главное преимущество компилируемых языков — это скорость исполнения. Поскольку они конвертируются в машинный код, они работают гораздо быстрее и эффективнее, нежели интерпретируемые, особенно если учесть сложность утверждений некоторых современных скриптовых интерпретируемых языков.</p><p>Низкоуровневые языки как правило являются компилируемыми, поскольку эффективность обычно ставится выше кроссплатформенности. Кроме того, компилируемые языки дают разработчику гораздо больше возможностей в плане контроля аппаратного обеспечения, например, управления памятью и использованием процессора. Примерами компилируемых языков являются <a href="https://tproger.ru/tag/c-language/">C</a>, <a href="https://tproger.ru/tag/cpp/">C++</a>, Erlang, <a href="https://tproger.ru/tag/haskell/">Haskell</a> и более современные языки, такие как <a href="https://tproger.ru/tag/rust/">Rust</a> и <a href="https://tproger.ru/tag/go/">Go</a>.</p><p>Проблемы компилируемых языков, в общем-то, очевидны. Для запуска программы, написаной на компилируемом языке, её сперва нужно скомпилировать. Это не только лишний шаг, но и значительное усложнение отладки, ведь для тестирования любого изменения программу нужно компилировать заново. Кроме того, компилируемые языки являются платформо-зависимыми, поскольку машинный код зависит от машины, на которой компилируется и исполняется программа.</p><h3>Интерпретируемые языки</h3><p>В отличие от компилируемых языков, интерпретируемым для исполнения программы не нужен машинный код; вместо этого программу построчно исполнят интерпретаторы. Раньше процесс интерпретации занимал очень много времени, но с приходом таких технологий, как <a href="https://tproger.ru/translations/basic-jit/">JIT-компиляция</a>, разрыв между компилируемыми и интерпретируемыми языками сокращается. Примерами интерпретируемых языков являются <a href="https://tproger.ru/tag/php/">PHP</a>, <a href="https://tproger.ru/tag/perl/">Perl</a>, <a href="https://tproger.ru/tag/ruby/">Ruby</a> и <a href="https://tproger.ru/tag/python/">Python</a>. Вот некоторые из концептов, которые стали проще благодаря интерпретируемым языкам:</p><ul><li>Независимость от платформы;</li><li><a href="https://ru.wikipedia.org/wiki/Рефлексия_(программирование)">Рефлексия</a>;</li><li><a href="https://ru.wikipedia.org/wiki/Система_типов">Динамическая типизация</a>;</li><li>Меньший размер исполняемых файлов:</li><li><a href="http://en.wikipedia.org/wiki/Scope_%28computer_science%29#Dynamic_scoping">Динамические области видимости</a>.</li></ul><p>Основным недостатком интерпретируемых языком является их невысокая скорость исполнения. Тем не менее, JIT-компиляция позволяет ускорить процесс благодаря переводу часто используемых последовательностей инструкции в машинный код.</p><h3>Бонус: байткод-языки</h3><p>Байткод-языки — это такие языки, которые используют для исполнения кода как компиляцию, так и интерпретацию. Java и фреймворк .NET — это типичные примеры байткод-языков. На самом деле, <a href="http://en.wikipedia.org/wiki/Java_virtual_machine">Java Virtual Machine</a> (JVM) — это настолько популярная виртуальная машина для интерпретации байткода, что на ней работают реализации <a href="http://en.wikipedia.org/wiki/List_of_JVM_languages">нескольких языков</a>. Кстати, недавно <a href="https://tproger.ru/news/jaotc-wtf/">стало известно</a>, что в новой версии Java будет также поддерживаться и статическая компиляция.</p><p>В байткод-языке сперва происходит компиляция программы из человекочитаемого языка в байткод. Байткод — это набор инструкций, созданный для эффективного исполнения интерпретатором и состоящий из компактных числовых кодов, констант и ссылок на память. С этого момента байткод передаётся в виртуальную машину, которая затем интерпретирует код также, как и обычный интерпретатор.</p><p>При компиляции кода в байткод происходит задержка, но дальнейшая скорость исполнения значительно возрастает в силу оптимизации байткода. Кроме того, байткод-языки являются платформо-независимыми, превосходя при этом по скорости интерпретируемые. Для них также доступна JIT-компиляция.</p><h3>Заключение</h3><p>Многие языки в наши дни имеют как компилируемые, так и интерпретируемые реализации, сводя разницу между ними на нет. У каждого вида исполнения кода есть преимущества и недостатки.</p><p>Вкратце, компилируемые языки являются самыми эффективными, поскольку они исполняются как машинный код и позволяют использовать аппаратное обеспечение системы. Однако это вводит дополнительные ограничение на написание кода и делает его платформо-зависимым. Интерпретируемые же языки не зависят от платформы и позволяют использовать такие техники динамического программирования, как метапрограммирование. Тем не менее, в скорости исполнения они значительно уступают компилируемым языкам.</p><p>Байткод-языки, в свою очередь, пытаются использовать сильные стороны обоих видов языков, и у них это неплохо получается.</p>]]></content:encoded>
    </item>
    <item>
      <title>Основные принципы программирования: стек и куча</title>
      <link>https://tproger.ru/translations/programming-concepts-stack-and-heap</link>
      <comments>https://tproger.ru/translations/programming-concepts-stack-and-heap?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Картофельный Повелитель]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/programming-concepts-stack-and-heap</guid>
      <description><![CDATA[<p>Разбираем ключевые концепции управления памятью: что такое стек (stack) и куча (heap), в чём разница между ними, как работает LIFO, указатели и сборщик мусора. Простое объяснение с примерами.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/programming-concepts-stack-and-heap">Основные принципы программирования: стек и куча</a>»</p>]]></description>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Обучение программированию]]></category>
      <category><![CDATA[Основные принципы программирования]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 02 Jan 2017 18:02:48 GMT</pubDate>
      <content:encoded><![CDATA[<p><b>Кратко о главном:</b><br />- Стек (stack) работает по принципу LIFO и управляется процессором — быстрый, но ограниченный по размеру<br />- Куча (heap) позволяет динамически выделять память и создавать глобальные переменные<br />- Стек используется для локальных переменных и вызовов функций, куча — для динамических структур данных<br />- В языках без сборщика мусора (C, C++) за освобождение памяти в куче отвечает программист<br />- Некорректное управление кучей приводит к утечкам и фрагментации памяти</p><p><i>Рассказывает Аарон Краус </i></p><p>Мы используем всё более продвинутые языки программирования, которые позволяют нам писать меньше кода и получать отличные результаты. За это приходится платить. Поскольку мы всё реже занимаемся низкоуровневыми вещами, нормальным становится то, что многие из нас не вполне понимают, что такое стек и куча, как на самом деле происходит компиляция, в чём разница между статической и динамической типизацией, и т.д. Я не говорю, что все программисты не знают об этих понятиях — я лишь считаю, что порой стоит возвращаться к таким олдскульным вещам.</p><p>Сегодня мы поговорим лишь об одной теме: стек и куча. И стек, и куча относятся к различным местоположениям, где происходит управление памятью, но стратегия этого управления кардинально отличается.</p><h2>Стек</h2><p>Стек — это область оперативной памяти, которая создаётся для каждого потока. Он работает в порядке LIFO (Last In, First Out),  то есть последний добавленный в стек кусок памяти будет первым в очереди на вывод из стека. Каждый раз, когда функция объявляет новую переменную, она добавляется в стек, а когда эта переменная пропадает из области видимости (например, когда функция заканчивается), она автоматически удаляется из стека. Когда стековая переменная освобождается, эта область памяти становится доступной для других стековых переменных.</p><p>Из-за такой природы стека управление памятью оказывается весьма логичным и простым для выполнения на ЦП; это приводит к высокой скорости, в особенности потому, что время цикла обновления байта стека очень мало, т.е. этот байт скорее всего привязан к кэшу процессора. Тем не менее, у такой строгой формы управления есть и недостатки. Размер стека — это фиксированная величина, и превышение лимита выделенной на стеке памяти приведёт к переполнению стека. Размер задаётся при создании потока, и у каждой переменной есть максимальный размер, зависящий от типа данных. Это позволяет ограничивать размер некоторых переменных (например, целочисленных), и вынуждает заранее объявлять размер более сложных типов данных (например, массивов), поскольку стек не позволит им изменить его. Кроме того, переменные, расположенные на стеке, всегда являются локальными.</p><p>В итоге стек позволяет управлять памятью наиболее эффективным образом — но если вам нужно использовать динамические структуры данных или глобальные переменные, то стоит обратить внимание на кучу.</p><h2>Куча</h2><p>Куча — это хранилище памяти, также расположенное в ОЗУ, которое допускает динамическое выделение памяти и не работает по принципу стека: это просто склад для ваших переменных. Когда вы выделяете в куче участок памяти для хранения переменной, к ней можно обратиться не только в потоке, но и во всем приложении. Именно так определяются глобальные переменные. По завершении приложения все выделенные участки памяти освобождаются. Размер кучи задаётся при запуске приложения, но, в отличие от стека, он ограничен лишь физически, и это позволяет создавать динамические переменные.</p><p>Вы взаимодействуете с кучей посредством ссылок, обычно называемых указателями — это переменные, чьи значения являются адресами других переменных. Создавая указатель, вы указываете на местоположение памяти в куче, что задаёт начальное значение переменной и говорит программе, где получить доступ к этому значению. Из-за динамической природы кучи ЦП не принимает участия в контроле над ней; в языках без сборщика мусора (C, C++) разработчику нужно вручную освобождать участки памяти, которые больше не нужны. Если этого не делать, могут возникнуть утечки и фрагментация памяти, что существенно замедлит работу кучи.</p><p>В сравнении со стеком, куча работает медленнее, поскольку переменные разбросаны по памяти, а не сидят на верхушке стека. Некорректное управление памятью в куче приводит к замедлению её работы; тем не менее, это не уменьшает её важности — если вам нужно работать с динамическими или глобальными переменными, пользуйтесь кучей.</p><h2>Сравнение стека и кучи</h2><p><b>Параметр | Стек (Stack) | Куча (Heap)</b><br />Принцип работы | LIFO (последний пришёл — первый ушёл) | Произвольный доступ через указатели<br />Скорость | Очень высокая (управляется ЦП) | Ниже (управляется программой или GC)<br />Размер | Фиксированный, задаётся при создании потока | Ограничен только физической памятью<br />Область видимости | Локальные переменные | Глобальные и динамические переменные<br />Управление | Автоматическое (ЦП) | Ручное (программист) или сборщик мусора<br />Риски | Переполнение стека (stack overflow) | Утечки и фрагментация памяти<br />Применение | Вызовы функций, локальные переменные | Динамические структуры, объекты, массивы</p><h2>Заключение</h2><p>Вот вы и познакомились с понятиями стека и кучи. Вкратце, стек — это очень быстрое хранилище памяти, работающее по принципу LIFO и управляемое процессором. Но эти преимущества приводят к ограниченному размеру стека и специальному способу получения значений. Для того, чтобы избежать этих ограничений, можно пользоваться кучей — она позволяет создавать динамические и глобальные переменные — но управлять памятью должен либо сборщик мусора, либо сам программист, да и работает куча медленнее.</p><h2>Часто задаваемые вопросы</h2><p><b>Что произойдёт, если стек переполнится?</b></p><p>Возникнет ошибка переполнения стека (stack overflow). Чаще всего это случается при бесконечной рекурсии — когда функция вызывает саму себя без условия выхода, каждый вызов добавляет новый фрейм в стек, пока память не закончится.</p><p><b>Зачем нужна куча, если стек быстрее?</b></p><p>Стек подходит только для данных с известным на этапе компиляции размером и локальной областью видимости. Если нужно создать объект, размер которого определяется во время выполнения программы, или передать данные между разными функциями и потоками — для этого необходима куча.</p><p><b>Как сборщик мусора связан с кучей?</b></p><p>Сборщик мусора (garbage collector) автоматически отслеживает объекты в куче и освобождает память, которая больше не используется. Он есть в языках вроде Java, Python, Go и C#. В языках без сборщика мусора (C, C++) программист обязан вручную вызывать free() или delete для освобождения памяти.</p><p><b>В каких языках программист должен управлять памятью вручную?</b></p><p>В C и C++ программист сам отвечает за выделение и освобождение памяти в куче (malloc/free, new/delete). Rust использует уникальный подход — систему владения (ownership), которая позволяет управлять памятью без сборщика мусора и без ручного освобождения.</p>]]></content:encoded>
    </item>
  </channel>
</rss>