Посоветуйте паттерн/архитектуру
Для меня твоя задача звучит как "я имею один декодер и кучу маппингов для декодирования". Маппинги сложить в коллекцию по версиям и обрабатывать общим декодером. Если декодер слишком сложный, то поделить на элементарные части и собирать по маппингу при запуске, в коллекцию класть декодеры сконструированные.
Можно ссылки на обработчики в словарь запихать, а в качестве ключей как раз номер блока и версию.
Пробуй банальное наследование. Для каждой версии сделай отдельный класс с каким-нибудь методом parse и фиксированным outputом. Если влом иметь столько разных классов, когда версий много - вместо этого делай объекты-теги и делай между ними switch (или мультидиспатч по тегу и по контенту)
Можно ссылки на обработчики в словарь запихать, а в качестве ключей как раз номер блока и версию.Допустим, блок А записывался методом Р1, а в версии 1.2.3 изменился на Р2. Любая версия >= 1.2.3 должна парсится по правилу Р2, а до этой версии - по Р1. Множество версий - неизвестно. Как ключ считать будешь?
А есть какая-нибудь тулза в идее, которая собирала бы исходник класса из его предков?
И куда деть статики?
По сабжу если версия известна, то Map. Заплнять можно через какой-нибудь ClassIndexer, их много разных. Если неизвестна, то список. Заплняется в одном месте в строгом историческом порядке
А нахер это нужно?это был вопрос к Айвенго, по его предложению.
И куда деть статики?
По сабжу если версия известна, то Map. Заплнять можно через какой-нибудь ClassIndexer, их много разных. Если неизвестна, то список. Заплняется в одном месте в строгом историческом порядкеМожешь поподробнее написать, а то я не улавливаю ход твоей мысли.
switch это нормальная практика. Ты же не указал язык. Я заодно тебе и double dispatch предложил, если с полиморфизмом хорошо заходит
Делаешь функцию, которая по версии генерит тег нужного типа. Тег параметризуешь типом входного и выходного блока. Можешь во внешний объект вынести обработку тега, а можешь прям внутри самого тега делать парсинг.
Map нужен чтобы понять, с какой версии начинать парсить. А сами парсеры я бы предложил разбить на 2 группы: собственно, парсеры (их в идеале должно быть немного, это только те, которые не могуть быть во второй группе) и делегаты, умеющие сконвертить свой вход во вход какому-нибудь персеру или делегату (чаще всего предыдущей версии). У первой группы может быть пошареный код и, соответственно, иерархия, а второй нахер не нужно. Здесь будет небольшая просада по производительности, но логические структуры должны почти полностью уйти. Ещё есть смысл поглядеть, как с этим справляются существующие системы разметки данных, поддерживающие версионирования.
А нахер это нужно?Ну кстати иногда может быть полезно для ковыряния в говнокоде (Yo-yo антипаттерн).
Допустим, блок А записывался методом Р1, а в версии 1.2.3 изменился на Р2. Любая версия >= 1.2.3 должна парсится по правилу Р2, а до этой версии - по Р1. Множество версий - неизвестно. Как ключ считать будешь?У тебя по сути есть кроме версии проги ещё и версии блоков.
Что мешает на каждую версию проги хранить список версий блоков?
Даже если хранить только изменения, написать функцию, которая по номеру версии проги будет выдавать список версий актуальных блоков, проблемы никакой не вижу.
Тут кстати и БД может прийтись в пору.
(if version > 100500 ... else ...). Как сделать чтобы было просто, красиво и удобно и т.д.?Прекрасные простые if и процедурное программирование великолепно решают эту задачу. Для чего тут городить ООП говнокод?
Посоветуйте паттернпаттерн называется Стратегия
Знаю такой. Имхо слишком много мороки под каждый блок заводить свою стратегию. К тому же некоторые блоки различаются только частично (скажем на 3ем уровне вложенности внесено небольшое изменение).
В зависимости от версии этой программы, она пишет данные в этот файл по-разному (причем формат от версии к версии меняется не целиком, а только отдельные блоки)что за формат? ini/xml/json ? или бинарная проприетарная хрень?
То есть у тебя есть бинарная таблица декодер/состояние и на каждом шаге ты по ней перемещаешься.
Прекрасные простые if и процедурное программирование великолепно решают эту задачу. Для чего тут городить ООП говнокод?Обфускация посредством архитектуры.
ага, gas factory.
json вперемешку с бинарными данными.
json вперемешку с бинарными данными.подобные форматы удобны тем, что позволяют делать манипуляцию без понимания семантики. всякие xslt/xml этим спекулируют.
для адаптации одного json к другому json можно использовать аналогичный подход:
1) определяем пути (аналог XPATH, можно просто склеивать названия имен узлов от корня до текущего), которые требуют трансформации, мапим путь или паттерн пути (это могут быть regexp) на трансформатор. трансформатор получает узел и выплевывает другой узел
2) енумерим узлы\атрибуты, поддерживаем текущий путь к узлу\атрибуту.
3) по пути можно поискать трансформатор, если такой есть, то запустить его и схавать выхлоп
мета-код (не спрашивайте что это за язык )
class JSonNode;
interface ITransformer
{
JSonNode Transform(JSonNode)
}
clas MySrcTransformer : ITransformer
{
JSonNode Transform(JSonNode node)
{
return node.Clone().ReplaceAttribute("src", "MySrc", "C:\windows.jpg");
}
}
TraverseNodes(in / out JSonNode node, string xpath, func)
{
xpath += "." + node.Name;
node.Children().ForEach(c = > TraverseNodes(c, xpath, func));
func(node, xpath);
});
dict<string, ITransformer> transformers;
transformers.Add("root.Image.*", MySrcTransformer()); /// << тут нужно положить всех трансформеров, которые соответствуют версии документа
JSonNode root = LoadDocument();
TraverseNodes(root, "root",
(node, path) = >
{
ITransformer t;
if (transformers.TryGetMatching(path, t))
{
node.CopyFrom(t.Transform(node));
}
});
SaveDoc(root);
Прекрасные простые if и процедурное программирование великолепно решают эту задачу. Для чего тут городить ООП говнокод?:facepalm:
каждое повторение кода увеличивает вероятность ошибки и усложняет отладку и исправление.
//версия 1.0
...
"block1" : [
{
"id" : "1",
"name" : "aaaa"
},
{
"id" : "2",
"name" : "bbbb"
}
],
...
"block2" : [
{
"id" : "1",
"amount" : 5
},
{
"id" : "2",
"amount" : 7
}
],
...
//версия 2.5
...
"block1" : [
{
"id" : "1",
"name" : "aaaa",
"amount" : 5
},
{
"id" : "2",
"name" : "bbbb",
"amount" : 7
}
],
...
:facepalm:каждое повторение кода увеличивает вероятность ошибки и усложняет отладку и исправление.давайте посчитаем, в каком варианте повторений кода больше
public class JustAnotherAbstractInterfaceFactory {
private final ImmutableSortedMap<String, FileMapping> mappings;
public JustAnotherAbstractInterfaceFactory() {
// reading mapping and set it to mappings
}
public List<JustAnotherBean> parseFile(String version, String content) {
FileMapping mapping = mappings.floorEntry(version);
if (mapping == null) {
throw new IllegalStateException("No mapping for version " + version);
}
return new Parser(mapping).parse(content);
}
}
public class Parser {
private final FileMapping mapping;
private final Map<Long, JustAnotherBean> beans;
public Parser(FileMapping mapping) {
this.mapping = mapping;
this.beans = new HashMap<Long, JustAnotherBean>();
}
public Collection<JustAnotherBean> parse(String content) {
for (String pathToBlock : mapping.getAllBlocks()) {
BlockMapping blockMapping = mapping.getBlockMapping(pathToBlock);
List<String> blocks = extractAllBlocks(content, pathToBlock);
for (String block : blocks) {
createOrUpdateBean(block, blockMapping);
}
}
return beans.values();
}
///
}
т.е. немного простого кода, который легко протестировать. Думаю при достаточно большом количестве полей в энтри он будет компактней ифов версии к третьей + легче тестировать. Из минусов - большая вложенность и возможная потеря при рефлекшне (можно избежать, если у нас будет не бин, а, скажем, Map).
Сейчас всё реализовано деревом с условиями (if version > 100500 ... else ...). Как сделать чтобы было просто, красиво и удобно и т.д.?Оставь как есть, самое простое решение - самое хорошее. Если бы у тебя была задача сделать какой-то абстрактный парсер в вакууме, оформить его в виде библиотеки и отдать другим разработчикам, чтобы они могли его расширять без правки кода самой библиотеки, то тогда мудреж имел бы смысл. Если у тебя просто есть код который ты сам пишешь и контролируешь, то введение полиморфизма просто усложняет код на ровном месте.
Ага, оставь как есть, пусть у тебя будет слаботестируемый разрастающийся кусок... кода.
Повторение кода в варианте if пока нету.
слаботестируемыйс чего ты это взял?
разрастающийся
а фактори не разрастаются? Потому что уже никто не может в них внести изменение? Потому что в них реализовано достаточно уровней сложности?
Повторение кода в варианте if пока нету.У тебя в каждой ветке if .. elseif повторяются примерно одни и те же действия по перекладыванию из JSON в объект X. Сколько веток - столько повторений.
:facepalm:Да вроде как раз жава известна кучей говнокода, который нужен только для того, чтобы преодолеть ее ущербность. Процедурный код как раз в таких простых случаях крайне просто сделать уникальным.
каждое повторение кода увеличивает вероятность ошибки и усложняет отладку и исправление.
с чего ты это взял?Для того чтобы проверить if .. elseif простыню тебе нужно скармливать объекты целиком и проверять, что получается то, что ты хочешь. И так для каждой версии. Если есть необязательные поля, то сложность набора достаточных тестов получается экспоненциальная. И в любом случае тебе доступны только end-to-end тесты, проверить конкретный if нельзя.
В случае разделения маппинга и реализации ты можешь отдельно протестить сам компонент, отдельно проверить маппинг. Сложность тестирования снижается в разы - разделяй и властвуй, да.
а фактори не разрастаются? Потому что уже никто не может в них внести изменение? Потому что в них реализовано достаточно уровней сложности?Потому что в них обычно не нужно вносить изменения. При появлении новой версии ты просто можешь написать маппинг (формальный) и собрать таким образом новый парсер из уже протестированных компонентов. Менять фактори тебе нужно будет только если добавится новый тип филда и то тестировать только его нужно будет.
Я вижу, что в твоем коде 16 строк тупо повторены два раза.да, да, спасибо что заметил. Исправил
Процедурный код как раз в таких простых случаях крайне просто сделать уникальным.Расскажите как, сверхчеловек-сан!
У тебя в каждой ветке if .. elseif повторяются примерно одни и те же действия по перекладыванию из JSON в объект X. Сколько веток - столько повторений.Начать с того, что 90% различий в формате скорее всего касаются добавления-удаления-переименования какого-нибудь поля. Эта задача решается смехотворно просто мердженьем дефолтного объекта с тем что распарсено (такие алгоритмы у вас спрашивают на собеседовании) и кучкой if-ов для файнтюнинга пары изменившихся полей.
а при чём тут одно к другому? На java можно прогать процедурно. И бывает не java OOP. Тот же CLOS, например.
А теперь расскажи как это протестировать
распространяться и стимулировать продажи друг друга.
Первый продукт можно называть enterprisify, он будет делать эквивалентные source to source преобразование java кода, добавляя в него уровней косвенности и "паттернов", заодно генерируя тавтологические юнит тесты. Инструмент должен сохранять форматирование и применять некоторые эвристики, чтобы было похоже на "рефакторинг" сделанный человеком. Возможен human-assisted режим.
Второй инструмент будет осуществлять обратные преобразования, его можно назвать dumbify. Тут важно, что dumbify будет на голову выше конкурентов, потому что он будет использовать секретные эвристики из enterprisify.
Можно продавать пак два в одном. Предполагается, что разработчик может пользоваться обоими инструментами (в разных ситуациях).
а вот допустим с такими данными твой алгоритм не справитсяно это не моя проблема, это - твоя проблема
идея в целом: подготавливаешь движок, в который уже можно пихать относительно маленькие стратегии, весь движок когда надо их вызовет и трансформирует документ
в случае json можно делать потоковые трансформации, как это делают функциональные flatten/map/filter/zip/sort/etc. это очень мощный фундамент, так они позволяют делать pipeline, то бишь офигенно все composable
можешь взять какой-нибудь mongoDB или что-то вокруг json (первая ссылка из гугла http://danski.github.io/spahql/#core_concepts ) для реюза или просто понять идею
тут нет серебряной пули: проще заложиться на конкретные виды преобразований. это как с SQL - вроде мощный, но далеко не все сценарии можно на нем реализовать
в твоем конкретном примере не ясно, в каком формате ты хочешь получить выхлоп.
если надо из v1.0 перегнать в v2.5, то надо создать трансформер, который сделать примерно следующее
JSonNode MyTransformer::Transorm(JSonNode node)
{
return new JSonNode().SetChildren(ZipWith(node["block1"].Children, node["block2"].Children, (a,b) => new JSonNode("name" = a["name"], "amount" = b["amount"]));
}
если надо из v2.5 перегнать в v1.0, то делаем так
JSonNode MyTransformer::Transorm(JSonNode node)
{
var result = new JSonNode()
result.AddChild("block1", FilterAttribute(node["block1"], "name"));
result.AddChild("block2", FilterAttribute(node["block1"], "amount"));
return result;
}
В случае разделения маппинга и реализации ты можешь отдельно протестить сам компонент,Т.е. протестировать код, который ничего не делает или делает то, в чем появление ошибки крайне маловероятно.
Сложность тестирования снижается в разы - разделяй и властвуй, да.
Теории сложности тестирования на сегодняшний день не существует (могу привести авторитетные ссылки). О каком снижении ты говоришь? В каких единицах? В попугаях? Как сравнивать два тестирования, какое больше какое меньше?
Т.е. протестировать код, который ничего не делает или делает то, в чем появление ошибки крайне маловероятно.Я думал, почему продукты гугл все хуже и хуже. Теперь становится понятно.
Т.е. протестировать код, который ничего не делает или делает то, в чем появление ошибки крайне маловероятно.Это как бы вся сущность программирования: разбитие большой задачи на несколлько мелких, которые мы умеем решать правильно и быстро
Я думал, почему продукты гугл все хуже и хуже. Теперь становится понятно.У тебя есть шанс! Возьми кредит в банке, создай гугль-киллер!
Оставить комментарий
kill-still
Есть задача: парсить файлы, которые пишет другая программа. В зависимости от версии этой программы, она пишет данные в этот файл по-разному (причем формат от версии к версии меняется не целиком, а только отдельные блоки). В результате парсинга вне зависимости от версии должен получиться объект фиксированного формата. Сейчас всё реализовано деревом с условиями (if version > 100500 ... else ...). Как сделать чтобы было просто, красиво и удобно и т.д.?