Страницы

Поиск по вопросам

Показаны сообщения с ярлыком best-practice. Показать все сообщения
Показаны сообщения с ярлыком best-practice. Показать все сообщения

воскресенье, 15 марта 2020 г.

Транзакции в коде

#алгоритм #разработка_игр #исключения #best_practice


Есть примерно такой код:

ПриВыходеИзВарпРежима()
{
  ДобавитьКораблиВКосмос();
  УстановитьУНихДефолтныеКоординаты();
  ...
  ВключитьЩиты();
  ОбнаружитьПротивников();
}


В методе ВключитьЩиты произошла ошибка, а значит, я хочу всё откатить назад.
С базами данных всё легко, там есть транзакции. А как быть с кодом? 
Может придумали что-то? Чтобы не писать кучу обратных шагов.
    


Ответы

Ответ 1



Попробуйте написать ваш код в стиле «опасные изменения — безопасный коммит» + иммутабельность. // изменения локальные корабли' = создать корабли с дефолтными координатами и включённым щитом(); локальный космос' = космос.ДобавитьКораблиИВернутьНовыйКосмос(корабли'); ... локальные противники' = обнаружить противников в (космос'); // безопасный коммит космос = космос' противники = противники' Если какая-то часть из изменений вылетит — она затрагивает лишь локальные объекты, которые съест garbage collector или RAII. Если все объекты у вас иммутабельны, то новый локальный космос — не расходная штука: он делит большую часть своих объектов со старым космосом.

Ответ 2



Для решения такой задачи используется паттерн Memento (Хранитель). С его помощью Вы сохраняете состояние объекта перед началом операций. А в случае ошибки - записываете сохраненное состояние обратно. Осталось только отловить наличие ошибки. Беспроигрышный вариант - через возвращаемые значения. Еще можно воспользоваться исключениями. Но в данном случае они не очень к месту, так как это вовсе даже не исключительная ситуация, а вполне себе игровая логика. Они только добавят лишние тормоза. И размажут логику по коду не хуже, чем это делает goto.

Ответ 3



Не совсем понятно, что именно вы хотите откатить назад. Состояние переменных, измененных в методе, в котором произошла ошибка? Если так, то самый простой путь - это сохранить состояние всех нужных вам переменных перед вызовом метода ВключитьЩиты(). После чего, в случае ошибки, использовать вернуться к сохраненному состоянию.

Ответ 4



Я видел такую методику. Перед выполнением критичного кода, делаем копию всех переменных/объектов. Потом выполняем код. Если произошла ошибка, то просто восстанавливаем все назад. В С++ это можно сделать удобно, если написать правильные конструкторы копирования. В чистом Си я видел другой прием. Хитрая череда goto. Где то так. ПриВыходеИзВарпРежима() { if (!ДобавитьКораблиВКосмос()) { goto space; } if (!УстановитьУНихДефолтныеКоординаты()) { goto coord; } ... if (!ВключитьЩиты()) { goto guard; } if (!ОбнаружитьПротивников()) {goto anemi;} return ok; anemi: ПровестиДиагностикуВторогоУровня(); guard: ПерекалиброватьЩиты(); coord: УбратьДефолтныеКоординаты(); space: УбратьКораблиСКосмоса(); return bad; } То есть, если происходит ошибка - переходим по goto на обработчики "откатки в к нормальному состоянию". Да, я вижу, что Вы не хотите писать "обратные шаги", но иногда без них просто невозможно.

среда, 12 февраля 2020 г.

Как лучше работать с конфигурациями в Ruby

#ruby #config #configuration #best_practice


Допустим, у нас есть некая структура конфигурации (взята из YAML, JSON, XML или просто
в виде Hash):  

configuration = {
  gmail: {
    username: 'example@gmail.com',
    password: 'pa$$word',
    host: 'imap.gmail.com',
    ssl: true,
    port: 993
  },
  ftp: {
    username: 'example@gmail.com',
    password: 'pa$$word',
    host: 'imap.gmail.com',
    ssl: true,
    port: 42
  }
}


Далее, на основе этой структуры мы достаём из неё данные:  

mail = Mail.new host: configuration[:gmail][:host], port: configuration[:gmail][:port],
username: configuration[:gmail][:username], password: configuration[:gmail][:password],
ssl: configuration[:gmail][:ssl]
ftp = FTP.new host: configuration[:ftp][:host], port: configuration[:ftp][:port],
username: configuration[:ftp][:username], password: configuration[:ftp][:password],
ssl: configuration[:ftp][:ssl]


Всё работает, но сам код получается «плохочитаемым». 
Т. е. он конечно понятен, но слишком много «сухого» текста, вместо привычного для
языка программирования кода.  

Поделитесь Best Practice, как правильно делать и использовать конфигурации в Ruby.
    


Ответы

Ответ 1



Если имена атрибутов Mail и FTP в вашем примере в точности соответствуют ключам в структурах в конфигурации - вы можете передавать в конструктор хеш напрямую: mail = Mail.new configuration[:gmail] ftp = FTP.new configuration[:ftp] Здесь важно помнить две вещи: Ключи и типы значений в конфигурации должны совпадать с атрибутами и типами значений атрибутов класса; Доступ к конфигурации должен быть только у вас - иначе злоумышленник может, оперируя структурой, создавать объекты с любыми атрибутами класса без ограничений.

Ответ 2



Ну, как минимум вы написали выбор поднабора из хэша с захардкоженными ключами. "Колонна" в вашем коде (которая получается, если код "подровнять"): mail = Mail.new host: configuration[:gmail][:host], port: configuration[:gmail][:port], username: configuration[:gmail][:username], password: configuration[:gmail][:password], ssl: configuration[:gmail][:ssl] # ^^^^^^^^^^^^^^^^^^ это ^^^^^^^^^^^^^^^^^^^^ ...на самом деле же просто хэшмап. Просто {} вокруг него писать оказалось необязательно, т. к. это последний аргумент вызова метода, и это особый случай в синтаксисе Ruby. Если в configuration[:gmail] нет лишних ключей, то можно сделать попросту вот так: Mail.new configuration[:gmail] ...а если лишние ключи хочется отрезать, и вы вооружены aсtivesupport'ом (гем такой, из состава Rails), есть Hash#slice: # Точечная загрузка ActiveSupport require 'active_support/core_ext/hash/slice' # В Rails необязательно, там он обычно весь уже загружен Mail.new configuration[:gmail].slice(:host, :port, :username, :password, :ssl) ...но обычно один набор параметров конфигурации используется в ровно одном месте (или если в нескольких, то как минимум одинаково: скажем, при вызове однотипных конструкторов), поэтому можно себе позволить просто не писать в конфигах лишние ключи. Это не конвенция, такая ситуация сложилась сама и она всех устраивает. "Best bractices" конфигураций, которые вы ждёте, этого всего в основном не касаются и они запакованы, в разных комбинациях, в гемы dotenv, figaro и config. Что в них типично встречается: Сокращённый синтаксис получения конфигурации ключей: a[:b][:c] => a.b.c Фоллбэк (когда конфига нет) к переменным среды в ENV (см. 12-факторные приложения) Конвенции по размещению конфигурационных файлов и их формату — кто на что горазд

воскресенье, 9 февраля 2020 г.

Нужно ли создавать отдельные классы для сущностей бд и rest запросов?

#база_данных #mvc #rest #best_practice


У меня есть контроллер, который принимает или возвращает объект юзера. Так же у меня
есть база данных, которая хранит юзера. Естественно, что не все поля объекта, которые
хранит бд должны отправляться сервером. Нужно ли создавать отдельные класс для сущности
бд, класс, который будет отправлен сервером, и класс, который будет выполнять бизнесс-логику
или можно просто создать 1 класс и уже перед отправкой решать какие поля добавлять
в JSON, а какие игнорировать?
    


Ответы

Ответ 1



Все зависит от тяжести случая. Представьте, у вас таблица с юзером одна, а форм где показывается пользовательсная информация две. А потом Вы захотите добавить третью, не задев первые две. В случае с отдельными классами на каждую форму(запрос), Вы добавите новый класс и новую форму, а в случае с одним классом Вам придется лезть в уже рабочий(протестированный) код, менять юнит тесты. А еще это можете быть не Вы сейчас, а Вы через 3 года, или Ваш коллега. С другой стороны, если приложение одноразовое, то игра не стоит свеч.

Ответ 2



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

воскресенье, 26 января 2020 г.

Переписать или отлаживать дальше? [закрыт]

#best_practice


        
             
                
                    
                        
                            Закрыт. На этот вопрос невозможно дать объективный ответ.
Ответы на него в данный момент не принимаются.
                            
                        
                    
                
                            
                                
                
                        
                            
                        
                    
                        
                            Хотите улучшить этот вопрос? Переформулируйте вопрос,
чтобы на него можно было дать ответ, основанный на фактах и цитатах, отредактировав его.
                        
                        Закрыт 3 года назад.
                                                                                
           
                
        
Написал прогу, тестовое задание для приема на работу. Код вышел крайне кривой.
Т.е. он-то работает, но малейшая ошибка (при изменении исходных данных или еще что-либо)
разбивает его вдребезги.  

Вот собственно хочу спросить совета у бывалых, что лучше делать в таких случаях:
пытаться дописать, довести до ума получившееся "УГ" или все-таки лучше взять и переписать
все на чистовик, абы сверкало и не глючило?  

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


Ответы

Ответ 1



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

Ответ 2



Часто прогеры мечутся между двумя крайностями: Первая крайность: пытаются заведомо нерабочий код сделать рабочим разнообразными примочками, мелкими правками и проч. В итоге код запутывается до невозможности Вторая крайность: перфекционизм - несмотря на то что код рабочий вылизывают код до потери пульса или же подгоняют под какой-нибудь приличный паттерн. Функционал при этом остается прежним, а трудозатраты растут. Я для себя выработал несколько правил: а. Если код работает - то стараюсь не вносить мелкие улучшения. Правило: "Не трогай то что работает!" б. Код подлежит замене, если его расширение/модификация привносит проблемы - это сигнал к пересмотру кода (даже если код работает). Правило: "модификации должны быть гладкими" в. Если править код, то надо править конкретно! Правило: "лучше 1 большое изменение, чем 10 маленьких" Исходя из этого я бы определил, что код автора подлежит замене - согласно правилам а) и б)

пятница, 27 декабря 2019 г.

Правильно ли добавлять в сущность посторонние методы в symfony

#php #ооп #symfony #symfony3 #best_practice


Изучаю symfony, использую версию 3.4
Возник такой вопрос. У меня есть таблица domains со списком доменов и соответственно
сущность для этой таблицы с полями таблицы и геттерами-сеттерами для них.
Появилась необходимость выяснить в коде, есть ли у домена кириллические символы (русские
буквы). И возник вопрос, корректно ли будет с точки зрения best practices добавить
в сущность AppBundle\Entity\Domains метод вроде:

public function isCyrillic(){
    return preg_match('/[\p{Cyrillic}]/u', $this->name) === 1 ? true : false;
}


А потом добавлять в эту сущность ещё и ещё подобных методов? Или в сущности должны
храниться только описания полей таблицы и геттеры-сеттеры для них? А другие методы
нужно выносить в сервисы или ещё куда-то?
Интересует ответ именно best practices, т.к. хочется писать код корректно и не лепить
велосипеды. Нужно мнение профессионала)
    


Ответы

Ответ 1



Приведенный вами метод вполне укладывается в представление Enitiy. Туда подходит всё что может содержать префикс is|get|set|has и прочие. Сервисы могут иметь подобные методы, но сами они stateless, поэтому придётся на вход подавать эту самую сущность, что не очень практично. Репозитории так же сам по себе являются stateless, и служат для работы уже с самими сущностями, так что туда тоже не стоит относит подобные методы.

Ответ 2



Это нарушит принцип SRP. Класс не должен иметь несколько обязанностей. Обязанность данного класс - хранить и получать данные. Метод isCyrillic добавляет классу новую обязанность. А это может привести к дублированию кода и увеличению сложности класса. Вы не сможете воспользоваться этим методом в других местах или вообще в других проектах. Поэтому я бы вынес этот метод в отдельный сервис.

четверг, 5 декабря 2019 г.

Версионирование микросервиса

#архитектура #best_practice #версионирование


Имеется микросервис (.Net core, но это не так важно). В нём реализованы новые фичи,
где-то поменялись DTO и эту реализацию требуется сделать новой версией (v2), с сохранением
старого функционала (v1).  

Были предложены следующие варианты:  


Сделать ветку v1 от текущего master, зафиксировать её и выложить отдельным инстансом.
При корректировке логики, затрагивающей обе ветки, делать cherry-pick соответствующих
коммитов.
Сдублировать логику v1 и v2 в одном решении, разнеся её по разным пространствам имён,
реализовать два контроллера - для v1 и v2.


Первый подход подразумевает инфраструктурные затраты. Второй уродлив донельзя в виде
кода.
Вопрос: что считается best practice? Возможно, есть какой-то третий (четвёртый, пятый)
подход, который мы не рассмотрели?
    


Ответы

Ответ 1



Предисловие Как вы сами понимаете, "единого" и/или "идеального" способа решения данной проблемы не существует, существуют лишь наиболее и наименее удобные в разрезе каждого конкретного случая подходы. Могу предложить тот, к которому методом проб и ошибок пришла команда разработки, в разрезе которой нахожусь я. Общие предложения (client-side и server-side) Предлагаю воспользоваться близким к первому из выдвинутых вами вариантов, т.е. разбить v1 и v2 версии вашего микросервиса на 2 независимых модуля (2 клиентских библиотеки на клиентской стороне и 2 единицы диплоймента на серверной) по совокупности качеств: возможность параллельного развития независимость разработки наличие возможности отказа от обратной совместимости между версиями сервиса При дальнейшем появлении последующих версий сервиса (раз у вас появилась вторая версия сервиса, то, скорее всего, позднее появится и третья) придерживайтесь аналогичной стратегии. Внесение feature, bug fix и иных доработок микросервиса: При версионированной разработке микросервиса следует выделять, по крайней мере, major и minor-версии (формата ${major-version}.${minor-version}): изменение minor-версии происходит в пределах той же версии сервиса изменение major-версии требует выделения новой версии сервиса Доработки следует выполнять, опираясь на следующие правила: доработка, не ломающая обратной совместимости, вносится с поднятием minor-версии в разрезе той же major-версии сервиса доработка, отламывающая обратную совместимость, потребует поднятия major-версии, то есть создания новой версии сервиса Client-side (предложения по клиентской части) Предоставление клиентских библиотек для работы с вашими микросервисами: Зачастую разработчики сервисов также предоставляют клиентскую библиотеку для работы с их микросервисами, которая предоставляет удобное API и инкапсулирует в себе детали интеграционного взаимодействия: валидирует на консистентность значения полей получаемого объекта основе отвалидированного объекта формирует и отправляет http-запрос опосредованной через load balancer на серверную часть вашего микросервиса, т.е. в web-приложение, обрабатывающее предназначенные для соответствующей версии сервиса запросы получает ответ на отправленный http-запрос и накладывает его на удобные для пользователя классы из API, предоставляя их в качестве возвращаемого значения В случае, если вы предоставляете клиентскую библиотеку для работы с вашим микросервисом, то в целевом решении её удобнее всего будет также дробить по версиям сервиса, но с некоторыми замечаниями: major-версия клиентской библиотеки должна соответствовать major-версии связанного с ней микросервиса (под major-версией подразумевается то, что вы в контексте вопроса просто называете версией) каждая major-версия библиотеки должна предоставлять свое API в отдельном "пространстве" namespace в C# package в Java ... Пояснение: Предоставляя API каждой версии в обособленном "пространстве" вы предоставляете пользователям возможность одновременно использовать несколько версий вашего API в одном модуле кода, что, может оказаться, особенно востребовано в переходный период между версиями. Например, когда для потребителя вашего сервиса целиковый перевод его модуля, уже использующего предыдущую версию, на новую версию сервиса за раз может оказаться трудоемкой, либо в особых случаях, вообще, не требующей полного перехода задачей. Server-side (предложения по серверной части) На серверной части выделяются отдельные единицы диплоймента (независимые артефакты) для каждой версии сервиса по совокупности качеств: возможность параллельного развития независимость разработки и поставки на сервера приложений (а, следовательно, и стенды) возможность одновременного использования n'го количества версий сервиса в разрезе одного сервера приложений наличие возможности поштучного/точечного введения в эксплуатацию и выведения из эксплуатации микросервисов (web-приложений, содержащих Endpoint'ы/Conteoller'ы для обработки запросов только в разрезе соответствующей версии сервиса) наличие возможности отказа от обратной совместимости между версиями микросервиса При дальнейшем появлении последующих версий сервиса (раз у вас появилась вторая версия сервиса, то, скорее всего, позднее появится и третья) придерживайтесь аналогичной стратегии. Пояснение: Для каждой версии микросервиса вы выделяете отдельный модуль/артефакт, который будет поставляться как отдельная единица диплоймента web-приложения. Каждая из данных единиц диплоймента будет подниматься в уникальном в разрезе версий микросервисов контексте: some/web/app/context/v1 some/web/app/context/v2 ... some/web/app/context/vn Данное решение может показаться не слишком изящным в виду факта версионирования на уровне контекста web-приложения (далеко не всем придется по нраву идея явного указания версии в url'е), но стоит привести несколько доводов в пользу данного решения, чтобы оно не казалось столь отталкивающим: в разрезе одного сервера приложений (WebSphere/WildFly/Tomcat/Jetty и т.д.) появляются возможности: одновременно комбинировать необходимые версии микросервисов независимо вводить в эксплуатацию и выводить из эксплуатации различные версии микросервисов появляется возможность L7 load balancing'а (L7 сетевой модели OSI/ISO) в разрезе каждой версии сервиса на соответствующие экземпляры web-приложений, на которых крутятся данные микросервисы, например, посредством Nginx: заводите отдельный upstream под каждую версию сервиса заводите отдельный location под каждую версию сервиса каждый из location'ов будет проксировать запрос на соответствующий его версии сервиса upstream upstream производит load balancing на одно из соответствующих версии сервиса web-приложение (которое обрабатывает запросы только определенной версии сервиса) даже если несколько web-приложений соответствующих различным версиям микросервисов развернуты в разрезе одного сервера приложений. Load balancing производится, например, одним из следующих алгоритмов: Round Robin IP-Hash Least connected (в этом же случае появятся издержки на стороне nginx на поддержание актуального списка активных connection'ов, т.е. http-соединений) ... Использование базы данных: В случае, когда необходимо использование персистентного хранилища данных для корректной работы вашего микросервиса, то для версионирования схемы данных можно воспользоваться одним из следующих способов: изоляция данных одной версии микросервиса от данных другой evolutionary database design Про второй способ версионирования схемы БД вы можете прочитать по ссылке, поэтому расскажу про первый, который является наиболее логичным решением в разрезе контекста данного ответа. В качестве способа изоляции данных можно посмотреть в сторону двух наиболее распространенных: различные БД + высокий уровень изоляции данных В данном случае, уровень изоляции будет зависеть от серверов, где хостятся БД для данных различных версий сервисов, если они различны, то даже одного сервера не возымеет эффекта на сервисы, данные которых он не хранит. - дополнительные, весомые затраты серверных ресурсов (CPU, RAM и ROM), уходящих на содержание сразу нескольких инстансов БД (возможно, даже различных БД) механизм изоляции, предоставляемый средствами используемой БД - отсутствие изолированности с точки зрения отказоустойчивости (падение одной БД скажется на работе всех версий сервисов) В общем случае, уровень изолированности будет зависеть от устойчивости к разделению в пределах БД (partition tolerance из CAP theorem) + практически полное отсутствие дополнительных затрат серверных ресурсов Например, в случае использования СУБД Oracle для хранения данных различных версий сервисов можно реализовать одним из следующих способов: использовать различные схемы данных таблицы, сиквенсы и иные сущности БД всех версий сервисов оставлять с одинаковым наименованием ради минимизации объема изменений в коде микросервиса между версиями над DataSource для работы с БД создать обертку, которая будет переключать контекст запроса на необходимую схему данных (ALTER SESSION SET CURRENT_SCHEMA) во избежание явного указания схемы данных в запросах к БД использовать механизм партиционирования (шардирования) для данных каждой версии сервиса использовать отдельно выделенную партицию, а в запросах к партиционированной таким образом таблице явно указывать значение столбца партиционирования (например, выделить столбец VERSION), тем самым неявно указывая СУБД Oracle на необходимость поиска лишь в определенном файле партиций, заставляя её игнорируя остальные, так как в них гарантированно отсутствуют данные для запрашиваемой версии сервиса В большинстве случаев, в соотношении стоимость-востребованность, второй вариант изолированности данных оказывается наиболее подходящим. Стоит заметить, что в случае, если у вас какая-то особая бизнес-логика, которая предполагает, что посредством более высокой версии сервиса вы должны иметь доступ к данным более низкой версии, то никто не запрещает написать периодически запускаемый (в частности, одноразовый) конвертер данных из хранилища, содержащего данные более низкой версии, в хранилище, содержащее данные с более высокой версией. Например, в случае СУБД Oracle конвертер можно оформить в виде PL/SQL хранимой процедуры P. S. Весь мой пост преподнесен несколько в разрезе терминологии java enterprise, но, надеюсь, что вышеописанное удастся сколько-нибудь адекватно наложить на архитектуру вашего .Net-приложения.

Ответ 2



Подобные ситуации затрагивают еще ряд вопросов: - будет ли развитие двух версий параллельно и если да, насколько долго? - насколько сильно отличается версия v2 от v1? Если развитие первой версии на уровне багфиксов, то первый вариант. Если параллельное развитие будет еще долго и версии отличаются сильно, то тоже первый вариант. Если параллельное развитие будет еще долго и версии отличаются не сильно, то можно новые методы добавить в старый контроллер с перфиксом в имени, а на внешней стороне решить на уровне роутинга запросов и сделать красивые пути вида /v1/ под капотом controller/action и /v2/ под капотом controller/v2Action.

воскресенье, 24 ноября 2019 г.

Правильно делать приватные методы Java статическими или нет? Плюсы и минусы каждого варианта?


В английской версии видел этот вопрос, но в русской версии не нашел. Часто некоторы
программисты используют private static методы, чтобы показать что этот приватный мето
не использует никаких переменных и методов класса, другие наоборот против этого подхода так считают его не правильным использованием ООП (речь только о приватных методах класса, не использующих никаких переменных класса и других не статических методов). 

То есть, что правильнее по вашему использовать так 

   public class MyClass {      
      private static void func1() { // со статик
        ...
      }
   }


или так

   public class MyClass {      
      private void func1() { // без статик
        ...
      }
   }


?

Я говорил о тех методах, которые не используют (и не могут использовать) поля напрямую
например метод boolean isValid(T str), который вызывается в других методах класса для множества различных объектов, и которые нужны исключительно только для этого класса и выносить их в отдельный Utils класс нет смысла.

Как пример такого метода это hugeCapacity в ArrayList из Oracle JDK 8:

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}


P.S. Любые цитаты и ссылки на известных Java авторов очень помогут решить вопро
(скажем, я знаю, что в ArrayList из Oracle JDK, где авторы довольно известные Josh Bloch  и Neal Gafter, используется private static методы). Может вы знаете другие подтверждения той или иной точки зрения? 

То есть идеальный ответ, это ответ с цитатами на книги / статьи известных Java авторов или тех кто занимался теорией ООП или проектирования. 
    


Ответы

Ответ 1



Руководствуйтесь смыслом, и только им. Строение ваших классов должно отображать н техническую возможность сделать так или иначе (иногда экземплярные методы можно объявить статическими), а отношения между объектами в доменной области. Это и правда база всего ООП (а где ж ещё следовать ООП, если не в Джаве?). Я не могу привести цитату из умной книги по этому поводу. Но сама идея делать сигнатур метода зависимой не от его смысла, а от подробностей его конкретной имплементации кажетс мне грубым хаком. Даже если это внутренний, приватный метод. Если в публичных методах, предназначенных другим, мы пишем правильно, то почему в методах, предназначенных для себя, писать неправильно? Для отступления от правильного дизайна нужны веские причины. У ArrayList такие причин есть: он используется в миллионах проектов, и среди них есть критические по времен куски, и там выигрыш пары тактов играет серьёзную роль. Но для большинства классов, которые мы пишем, количество вызовов в наших программах не исчисляются десятками тысяч в секунду, и отступление от правильного дизайна вряд ли оправдано. Если метод относится к конкретному объекту, то объявляйте его экземплярным методо вне зависимости от того, обращается он к нестатическим полям и this или нет. Если мето общий для всего класса, и не имеет смысла в контексте отдельного экземпляра, объявляйте его статическим. Если метод вовсе не относится к классу, вынесите его во вспомогательный класс. Пример: оружие рыцаря — меч. Да, у всех рыцарей одинаковое оружие. Тем не менее метод, выдающий оружие — это очевидно экземплярный метод. Далее, рыцари не пользуются кинжалами. Поэтому геттер кинжала у рыцаря возвращает null. Это тоже экземплярный метод. class Knight : Warrior { private Weapon createMainWeapon() { return new Sword(); } private Dagger getDagger() { return null; } } Далее, количество рыцарей. Соответственно оно не имеет смысла в контексте одног рыцаря, значит, является статическим методом. class Knight : Warrior { static int numberOfKnights; public Knight() { numberOfKnights++; } public static int getNumber() { return numberOfKnights; } } Ну и наконец, метод, который выясняет, в порядке ли амуниция перед битвой, вообще не относится к сфере деятельности рыцаря. Пусть этим займётся оруженосец! class Squire { Knight master; public prepareForBattle() { ... } }

Ответ 2



Во-первых, для начала хорошо бы определиться, а нужен ли этому классу данный метод? Скажем, есть у нас класс машины и метод по переводу километров в мили. public class Car { private static double convertToMiles(double km){ return km*0.621371192; } } Очевидно, что этот метод вообще не нужен классу и его хорошо бы вынести в отдельный final Utils подобный класс, так как может быть использован много где. Renaud Waldura в "The Final Word on Final" пишет: Since a final method is only implemented in the declaring class, there is no need to dynamically dispatch a call to a final method, and static invocation can be used instead. The compiler can emit a direct call to the method, bypassing entirely the usual virtual method invocation procedure. Because of this, final methods are also candidates for inlining by a Just-In-Time compiler or a similar optimization tool. (Remember, private/static methods are already final, therefore always considered for this optimization.) Я лично делаю по возможности static, чтобы показать, что метод не зависит от состояни класса и никак на него не влияет. В IDE такой метод будет выделен italic шрифтом, что позволяет понять о его независимости от состояния класса, даже не заглядывая внутрь метода.

Ответ 3



Для более глубокого понимания добавлю исторический контекст вообще о статически методах. Некоторые исследователи вопроса вообще считают все подобные методы злом в ООП. Когда тема трудна для понимания, то один из лучших способов - обратиться к истории Java произошла от Smalltalk. Это знают все кто хоть один день потратил на его изучение В языке Smalltalk (с 1971 года) уже были статические методы - правда они там идут по названием "методы класса". Откуда же они появились там? Smalltalk имеет еще одного предка - Simula (с 1965 года). Оказывается и в Simula были такие методы - только там они идут под названием "свободный блок". Но в свою очередь кто "протащил" с язык Симула эти "свободные блоки"? А предок Симулы - Алгол. Потому что Симула изначально надмножество Алгола 60. Так что вот откуда ноги растут: а) Алгол procedure Absmax(a) Size:(n, m) Result:(y) Subscripts:(i, k); value n, m; array a; integer n, m, i, k; real y; comment The absolute greatest element of the matrix a, of size n by m is transferred to y, and the subscripts of this element to i and k; begin integer p, q; y := 0; i := k := 1; for p := 1 step 1 until n do for q := 1 step 1 until m do if abs(a[p, q]) > y then begin y := abs(a[p, q]); i := p; k := q end end Absmax б) Simula Begin Class Glyph; Virtual: Procedure print Is Procedure print; Begin End; Glyph Class Char (c); Character c; Begin Procedure print; OutChar(c); End; Glyph Class Line (elements); Ref (Glyph) Array elements; Begin Procedure print; Begin Integer i; For i:= 1 Step 1 Until UpperBound (elements, 1) Do elements (i).print; OutImage; End; End; Ref (Glyph) rg; Ref (Glyph) Array rgs (1 : 4); ! Main program; rgs (1):- New Char ('A'); rgs (2):- New Char ('b'); rgs (3):- New Char ('b'); rgs (4):- New Char ('a'); rg:- New Line (rgs); rg.print; End; Таким образом ООП изначально было надстройкой над императивным - поэтому совсем "выпилить статические методы не удастся. В институциональной экономике есть такое наблюдение приоритет/важность правила определяется только одним - стоимостью его изменения (или отмены). "Отменить" статические методы будет стоить человечеству столько человеко-лет, что видимо они с нами навсегда. Мы только можем уменьшить их использование в собственном коде с целью более удобной работы с объектами. Что касается именно приватных статических методов - к общим недостаткам статически методов добавляем еще и приватность. Очень редко встречал такие методы в коде. Сам не использую, так как стараюсь вообще минимизировать статические методы в коде и отношу себя к сторонникам идей Егора Бугаенко.

Ответ 4



Сразу обозначу ИМХО: метод не должен быть статическим и в посте стараюсь описат эту точку зрения. Каждый сам творит свой код. Может быть это актуально для оптимизации, но не для ООП. Получается это не метод, а процедура. Цитата из интервью David West: "Класс ничего не должен делать" Егор: Но в Java классы — не просто формочки для объектов. Мы туда помещаем методы… Дэвид: А зря! Егор: Вот в этом и мой вопрос! Дэвид: Ну, как я попытался сказать в книге, класс ничего не должен делать. В Smalltalk есть методы классов, но не надо их использовать. Их использование — возвращение к командному способу мышления. Вы берёте то, что должно быть ответственностью объектов, и по каким-то причинам пытаетесь запихнуть это всё в класс. И в итоге класс делает многое за объекты. интервью полностью Все сводиться к одному вопросу, почему этот метод должен принадлежать классу, а не объекту? Сейчас подумал, что эта цитата не в тему вопроса, но пусть останется. Я не совсем понимаю с какой целью делать приватный метод статическим. Если только для оптимизации. Если не брать в счет оптимизацию, то метод оперирует полями объекта (просто передан они будут как параметры). И тут можно вспомнить "чистый код". Чем меньше у метода параметров, тем проще он читается. Значит можно сделать его не статическим и использовать не параметры, а поля объекта. Если приватный метод не получает никаких параметров и не обращается к переменны объекта, то не совсем понимаю, что он будет делать. Update: Я говорил о тех методах, которые не используют (и не могут использовать) поля напрямую, например метод boolean isValid(T str), который вызывается в других методах класса для множества различных объектов, и которые нужны исключительно только для этого класса и выносить их в отдельный Utils класс нет смысла. Валидность объекта может определить сам объект и тут нужен "информационный эксперт и тогда суть дженерик метода уходит в каждый класс, т.е. вместо статического метода валидности любого объекта мы спросим сам объект валиден ли он.

Ответ 5



Возможно, это самое краткое пояснение смысла использования static методов. Статические методы следует применять в двух случаях. • Когда методу не требуется доступ к данным о состоянии объекта, поскольку вс необходимые параметры задаются явно (например, в методе Math.pow ()). • Когда методу требуется доступ лишь к статическим полям класса. Хорстманн, Кей С. Java. Библиотека профессионала, том 1. Основы. 10-е изд. стр.157

Ответ 6



Первым долгом хочу сказать это мое мнение это мой опыт !!! (Я думаю что вы знаете суть обычного и статического метода поэтому не пишу определение.) Статистический метод - это функциональный подход. В объектно-ориентированном проектирование это влияет на разработку то есть сопровождение сложно , мышление меняется на функциональность и так далее (общий ответ). Ну с точки зрения с памяти , обычный или статический метода нет потери почему: В памяти ".NET/Java" используется техника Flyweight (даже у этого техники есть паттерн но суть не в этом). Это техника экономит оперативной памяти (я не буду подробно объяснять технику) , и вот здесь обычный или статический метод не влияет никуда (с точки зрения памяти) , потом что каждого класса выделяется один объект(это технический имя) который методы содержатьс в этом объекте каждый экземпляр только содержит обычный Fields/Properties (изменчивый) и эти экземпляры ссылаются на этот объект , вот и краткие суть. (здесь подразумевается технический влияние). Хочу затронуть на один момент. Проблема в том что по-любому обычный или статически метод нельзя нагружать большой ответственность или большой объем кода (один из важных принципов). Я думаю что не надо так критически смотреть на эту тему. Не буду по философски сказку рассказывать. Ну вот разделил на две части (анализируйте). Ну и могу рекомендовать философскую книгу по этим темам "Object Thinking David West здесь подробно объясняется чистый объектно-ориентированное программирование/мышление архитектурное различие и такие темы как статический , обычные (методы , классы) и так далее.

Ответ 7



Нет неправильно. Точнее неправильно делать приватные методы класса статическими Технически конечно это можно делать, но по смыслу есть случаи когда это вредно и не нужно. Приведу пример: часто вижу, в том же самом Android, когда объявляется новый супер-пупе API, то при изучении исходников, выясняется, что просто сделали ранее приватный мето в классе публичным. То есть Google активно использует приватные нестатические метод в качестве способа управления версиями. Смысл простой, есть приватный метод, который скрыт внутри класса и активно используется нормальными публичными методами, таким образом метод проходит своеобразную обкатку в реальных условиях. Далее после изучения багтреков команда разработчиков решает вывести наружу метод: объявляется сигнатура метода как public и опля готово - прогеры получают в руки новый инструмент API. Считаю, что это очень хорошая практика. И хотя бы ради такого способа объявлять приватный метод статическим не стоило бы.

понедельник, 8 июля 2019 г.

Best Practise - python logger.debug различные yровни логгирования debug

Какие существуют рекомендации по написанию логгирования для режима debug в коде? Как правильно реализовать различные уровни (от 1 до 5 по уровню деталиазации) логгирования для указного режима? Например при включении debug_level <= 3 выводить сообщения в файл логгирования, а если debug_level > 3 тогда выводить debug информацию только на консоль.


Ответ

Привожу простой пример логгирования в консоль и файл с кастомный форматом логов:
def get_logger(name, file='log.txt', encoding='utf8'): import sys import logging
log = logging.getLogger(name) log.setLevel(logging.DEBUG)
formatter = logging.Formatter('[%(asctime)s] %(filename)s[LINE:%(lineno)d] %(levelname)-8s %(message)s')
fh = logging.FileHandler(file, encoding=encoding) fh.setLevel(logging.DEBUG)
ch = logging.StreamHandler(stream=sys.stdout) ch.setLevel(logging.DEBUG)
fh.setFormatter(formatter) ch.setFormatter(formatter)
log.addHandler(fh) log.addHandler(ch)
return log
Пример использования:
log = get_logger('my_log') log.debug('Start')
timeout = 1000 log.info('Timeout %s', timeout)
log.warn('Not found time!') log.error('Error while requests')
log.debug('End')
Результат в консоли и в файле:
[2017-04-03 13:34:25,047] FOO_TEST_TEST.py[LINE:32] DEBUG Start [2017-04-03 13:34:25,047] FOO_TEST_TEST.py[LINE:35] INFO Timeout 1000 [2017-04-03 13:34:25,048] FOO_TEST_TEST.py[LINE:37] WARNING Not found time! [2017-04-03 13:34:25,048] FOO_TEST_TEST.py[LINE:38] ERROR Error while requests [2017-04-03 13:34:25,048] FOO_TEST_TEST.py[LINE:40] DEBUG End

Уровень логирования задается не случайно, это является фильтром, например если в get_logger подправить строку для ch и изменить уровень с DEBUG на ERROR, то в консоль попадут логи с серьезностью от ERROR и выше:
ch = logging.StreamHandler(stream=sys.stdout) ch.setLevel(logging.ERROR)
В консоли будет только:
[2017-04-03 14:04:02,742] FOO_TEST_TEST.py[LINE:40] ERROR Error while requests
В файле будет полный лог как в первом примере

вторник, 4 июня 2019 г.

Транзакции в коде

Есть примерно такой код:
ПриВыходеИзВарпРежима() { ДобавитьКораблиВКосмос(); УстановитьУНихДефолтныеКоординаты(); ... ВключитьЩиты(); ОбнаружитьПротивников(); }
В методе ВключитьЩиты произошла ошибка, а значит, я хочу всё откатить назад. С базами данных всё легко, там есть транзакции. А как быть с кодом? Может придумали что-то? Чтобы не писать кучу обратных шагов.


Ответ

Попробуйте написать ваш код в стиле «опасные изменения — безопасный коммит» + иммутабельность.
// изменения локальные корабли' = создать корабли с дефолтными координатами и включённым щитом(); локальный космос' = космос.ДобавитьКораблиИВернутьНовыйКосмос(корабли'); ... локальные противники' = обнаружить противников в (космос');
// безопасный коммит космос = космос' противники = противники'
Если какая-то часть из изменений вылетит — она затрагивает лишь локальные объекты, которые съест garbage collector или RAII.

Если все объекты у вас иммутабельны, то новый локальный космос — не расходная штука: он делит большую часть своих объектов со старым космосом.

суббота, 23 марта 2019 г.

Переписать или отлаживать дальше? [закрыт]

Написал прогу, тестовое задание для приема на работу. Код вышел крайне кривой. Т.е. он-то работает, но малейшая ошибка (при изменении исходных данных или еще что-либо) разбивает его вдребезги.
Вот собственно хочу спросить совета у бывалых, что лучше делать в таких случаях: пытаться дописать, довести до ума получившееся "УГ" или все-таки лучше взять и переписать все на чистовик, абы сверкало и не глючило?
Просто я так подумал, если я так все время буду делать, это ж сколько времени уйдет даром, если так вот код переписывать всякий раз, когда мне в нем что-то не нравится.


Ответ

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