Конкурс окончен, смотрите результаты в конце вопроса.
Я думаю, многие из вас видели вопросы, состоящие из просьбы перевода программы с одного языка на другой. Давайте-ка покажем, как делать такие вещи правильно! В нашем конкурсе мы исходим из такой простой программы на Паскале:
program test;
var a, b, c: integer;
begin
readln(a);
readln(b);
c := a + b;
writeln(c);
end.
(Она же на ideone.)
Задание состоит в следующем: вы должны перевести программу на любой-язык так, чтобы сохранить как можно больше от исходного текста программы. В качестве целевого языка, понятно, исключаются языки группы Паскаля (Delphi, Algol, Oberon, Modula, etc., все языки, в которых используется begin/end для группировки команд в составную команду).
Вы можете дописывать конструкции до и после данного в условии текста, но не внутри его (точнее, можете и внутри, но это будет считаться изменением — смотрите ниже условия подсчёта). Сам текст желательно менять как можно меньше.
Ограничения: Строки исходной программы между begin и end должны сохранять свой смысл. Они должны выполняться, и при их выполнении должно происходить в общих чертах то же, что и в исходной программе: readln должно считывать значения с консоли, c := a + b должно складывать значения двух переменных (или что там есть в вашем языке) в третью, writeln должно выводить правильный результат на консоль. (Это означает, что вы не можете просто закомментировать код первоначальной программы.)
Определение победителя: Выигрывает код, в котором исходный текст менее всего изменён по сравнению с полным первоначальным вариантом (количество добавленных символов + количество удалённых символов + количество изменённых символов), считая и строки с program и end.. Разница в больших/маленьких буквах, а также замена символа на одинаковый по начертанию (например, русское «с»/английское «c») считается за пол-символа. Если два решения имеют одинаковое количество отличий (например, ноль), выигрывает то, у которого меньше добавленного кода (в символах). Если несколько решений одинаковы и по этому критерию, выигрывает то решение, которое получит больше голосов (как обычно, «за» минус «против»).
В частности, полное совпадение кода выигрывает у неполного независимо от количества подготовительного кода.
Для того, чтобы было легче проверять ваш код, старайтесь публиковать ссылку на онлайн-компилятор с вашим кодом. Код должен компилироваться без ошибок (пусть даже с предупреждениями) и правильно работать в диапазоне входных чисел от 0 до 1000.
Продолжительность конкурса — 1 неделя.
Для исключения разночтений, при неясности в правилах пожалуйста переспрашивайте в комментариях или в чате, посвящённом code golf
Для примера, вот внеконкурсное решение на plain TeX:
ewcount\tmp
ewcount\c
\def\uncatcodeletters{\uncatcoderange{`a}{`z}\uncatcoderange{`A}{`Z}}
\def\uncatcoderange#1#2{%
\tmp=#1 \advance\tmp -1
\loop\advance\tmp 1 \expandafter\catcode
umber\tmp=11 \ifnum\tmp<#2
epeat}
\def\s{\begingroup\uncatcodeletters\shlp}
{\def~ := #1 + #2;{%
\global\expandafter\c\csname #1\endcsname
\global\expandafter\advance\expandafter\c\csname #2\endcsname
\endgroup}
\global\let\shlp=~}
\def\inp{\begingroup\uncatcodeletters\inphelper}
\def\inphelper eadln(#1);{\endlinechar=-1 \escapechar=-1
\expandafter\inphelperi\csname #1\endcsname\endgroup}
\def\inphelperi{\global
ead16 to }
\def\out{\begingroup\uncatcodeletters\outhelper}
\def\outhelper riteln(#1);{\message{\expandafter\the\csname #1\endcsname}\endgroup}
\let\DEF\def \let\END\end
\catcode`r=13 \let r=\inp \catcode`w=13 \let w=\out
\catcode`p=14 \catcode`v=14 \catcode`b=14
\let~=\catcode ~`c=13 \let c=\s ~`e=13 \DEF end.{\END}
program test;
var a, b, c: integer;
begin
readln(a);
readln(b);
c := a + b;
writeln(c);
end.
(Если кому интересно, гольфированный вариант.) Транскрипт компиляции:
~>tex golf.tex
This is TeX, Version 3.14159265 (MiKTeX 2.9 64-bit)
(golf.tex
a=5 % <-- 5 введено с консоли
b=8 % <-- 8 введено с консоли
13 )
No pages of output.
Transcript written on golf.log.
Просьба к отвечающим писать в начале решения ваш язык, количество изменённых символов и количество символов в подготовительном коде. Также просьба давать не более одного решения на ответ.
Обновление: Конкурс окончен, вот результаты.
Побеждает ответ @Mike, сумевший уложиться в 78 подготовительных символов, и не поменять ни символа в исходном коде.
Другой ответ того же автора выглядит чрезвычайно изящно (подключение паскалевского синтаксиса как внешний модуль, хей!), и почти выиграл приз зрительских симпатий, но проигрывает по количеству символов вследствие своей большей общности. Оба решения пользуются особенностью языка Perl, который в своих модулях позволяет предобработку текста на Perl самим Perl'ом. Мощный язык, мощные средства управления синтаксисом, заслуженная победа.
Второй в списке победителей — ответ @Qwertiy. Это решение продолжает идею «получить текст как строку, обработать, чтобы получился код на нужном языке, и выполнить над ней eval», с симпатичной, очень техничной и компактной реализацией (регулярки!).
Приз зрительских симпатий получает неожиданный ответ @kmv. В этом решении текст исходной программы не объявляется строкой, а «вытягивается» из кода функции! (Это, формально говоря, решение не по стандарту, но фактически в распространённых браузерах toString() работает именно так.)
Третье место получает решение @pavel с комбинацией Unix shell/C, которое обходится без eval за счёт замены строк до компиляции и использования препроцессора C. Такой подход позволяет справиться с двоеточиями, которые вызывают затруднения для препроцессора у чистых решений на C/C++.
Вообще, идея со строкой и eval оказалась наиболее популярной, её реализуют также ответы @edem Perl, построчная замена, практически интерпретация, @Red Skotina (замена строк на Питоне, построчная адаптация текста, оставаясь в рамках правил, хотя и на грани), @gil9red (то же, но более универсально, Питон), @nuts119 на C# (да, в C# можно сделать eval, вы не знали?) и @Streletz на Java (интерпретатор из сторонней библиотеки).
Тему интерпретации продолжает ещё одно решение @nuts119 на C# с использованием DataTable как arithmetic engine. Это решение, при всей его сложности, имеет дальний прицел на построение полноценного интерпретатора.
Оставшиеся решения на чистом C/C++ и Javascript/Typescript без eval вынуждены модифицировать исходный текст, хотя они смогли обойтись минимальным количеством изменений. Из этих решений наилучшие с одним удалённым символом решения @kmv (C, препроцессор, использование битовых полей) и @Qwertiy (C++, тонкости препроцессора). Интересно, что эти оба решения убирают из исходного текста соседние символы: из := убрано в первом случае двоеточие, а во втором — знак = (!).
Оставшиеся четыре решения (@Qwertiy, typescript, @pavel, C++, препроцессор (заработало больше голосов, чем победитель), снова @Qwertiy, javascript и @Grundy, C, препроцессор) меняют больше символов в исходном коде, но также интересны и стоят вашего внимания.
Большое спасибо всем, кто принимал участие в конкурсе!
Таблица лидеров: (спасибо @Grundy за адаптацию скрипта и @jfs за идею)
function getAnswers(questionId, answer_filter, page) {
return jQuery.ajax({
url: '//api.stackexchange.com/2.2/questions/' + questionId + '/answers?page=' + page + '&pagesize=100&order=desc&sort=activity&site=ru.stackoverflow&filter=' + answer_filter,
method: "get",
dataType: "jsonp",
crossDomain: true
}).then(function(data) {
if (data.has_more) {
return getAnswers(questionId, answer_filter, page + 1).then(function(d) {
return data.items.concat(d.items);
})
}
return data.items;
});
}
function getAuthorName(e) {
return e.owner.display_name
}
function process(items) {
return items.map(function(item) {
var matched = item.body.match(/\s*(.+?)\s*?,.*?(\d+),.*?(\d+)\s*?(?:[.;,(].*)?<\/h/);
if (matched) {
return {
lang: matched[1],
setup: +matched[2],
changes: +matched[3],
link: item.share_link,
author: getAuthorName(item)
};
} else {
return {
lang: "N/A",
setup: "N/A",
changes: "N/A",
link: item.share_link,
author: getAuthorName(item)
}
}
});
}
function sort(items) {
return items.sort(function(a, b) {
if (a.lang == "N/A") return 1;
if (a.changes != b.changes) return a.changes - b.changes;
return a.setup - b.setup;
})
}
function fillTemplate(sortedItems) {
$('#leadership').append(sortedItems.map(function(item, index) {
return $('').append($('').html(index + 1))
.append($(' | ').html(item.author))
.append($(' | ').html(item.lang))
.append($(' | ').html(item.setup))
.append($(' | ').html(item.changes))
.append($(' | ').append($('').attr('href', item.link).text('Link')));
}));
return sortedItems;
}
var QUESTION_ID = 526265,
ANSWER_FILTER = "!4*SyY(4Kifo3Mz*lT",
startPage = 1;
getAnswers(QUESTION_ID, ANSWER_FILTER, startPage)
.then(process)
.then(sort)
.then(fillTemplate);
#leadership {
border-collapse: collapse;
}
#leadership td,
#leadership th {
padding: 5px;
}
#leadership th {
text-align: center;
}
Таблица лидеров
|
Автор |
Язык |
Подготовка |
Изменено |
|
Ответ
Раз в конкурсе участвует размер, вот еще вариант (лично мне нравится меньше, потому как большая ориентировка именно на этот код): perl, подготовка 97, измененных 0 perl, подготовка 78, измененных 0 sub r{$_[0]=<>}
sub wr{print@_}
$_=q. program test;
var a, b, c: integer;
begin
readln(a);
readln(b);
c := a + b;
writeln(c);
end. ;s/\b(\w)\b/\$$1/g
;s/^\S.*|.{4}n|://gm
;eval
Тест на ideone В коде происходит:
Создаем функции r/wr аналогичные по действию паскалевским read/write ln.
$_=q. присваивает "переменной по умолчанию" весь текст до следующего знака .
Все последующие строки работают с этой "переменной по умолчанию", так как явных переменных в них не указано.
Перед переменными (однобуквенными словами) ставим знак $
Удаляем строки program, var, begin, end, части названий функций (readln=r, writeln=wr), двоеточия
Выполняем полученный текст: r($a);
r($b);
$c = $a + $b;
wr($c);
Столкнулся с тем, что требуется реализовать множественное условие, которое в других языках я бы реализовал с помощью конструкции switch-case. В Python мне приходится расписывать всё через условия if-elif-else. Это мне кажется довольно неудобным. Есть ли более удобный способ записи подобных условий? Например, у меня есть единицы измерения и в зависимости от выбранной мне нужно вернуть соответствующий множитель: def get_multiplier(unit):
if unit == 'mm':
return 10**-3
if unit == 'cm':
return 10**-2
if unit == 'dm':
return 10**-1
if unit == 'm':
return 1
if unit == 'km':
return 10**3
raise ValueError('Undefined unit: {}'.format(unit))
Ответ
Для начала, ничего особенно плохого в использовании конструкции if-elif-else нет При желании можно найти несколько альтернатив.
Использование словарей Довольно распространённый способ организации конструкции switch-case в Python — это использование словаря. Проще показать на примере: unit_to_multiplier = {
'mm': 10**-3,
'cm': 10**-2,
'dm': 10**-1,
'm': 1,
'km': 10**3
}
Для того, чтобы получить нужный множитель в этом случае требуется лишь взять значение по ключу: try:
mult = unit_to_multiplier['cm']
except KeyError as e:
# можно также присвоить значение по умолчанию вместо бросания исключения
raise ValueError('Undefined unit: {}'.format(e.args[0]))
Если вы твёрдо уверены, что значение всегда будет присутствовать в словаре, можете опустить блок try-except и быть готовым ловить исключение в другом месте. Некоторой вариацией этого подходя будет предварительная проверка значения в условии: if unit in unit_to_multiplier:
mult = unit_to_multiplier[unit]
else:
# обработка отсутствия значения в словаре
В Python принято использовать подход, звучащий примерно так: "лучше попробовать и получить ошибку, чем каждый раз спрашивать разрешение", поэтому более предпочтительный подход с использованием исключений. Если хочется использовать значение по умолчанию в случае, если ключ отсутствует, удобно использовать метод get mult = unit_to_multiplier.get('ultra-meter', 0)
Если словарь вам требуется один раз, можно объединить эти выражения в одно: unit_to_multiplier = {
'mm': 10**-3,
'cm': 10**-2,
'dm': 10**-1,
'm': 1,
'km': 10**3
}.get('km', 0)
На этом возможности этого подхода не заканчиваются. Можно использовать условные выражения в качестве ключей словаря: def get_temp_description(temp):
return {
temp < -20: 'Холодно',
-20 <= temp < 0: 'Прохладно',
0 <= temp < 15: 'Зябко',
15 <= temp < 25: 'Тепло',
25 <= temp: 'Жарко'
}[True]
Этот словарь после вычисления будет иметь два ключа True и False. Нас интересует ключ True. Будьте внимательны, что условия не перекрываются! Подобные словари могут проверять произвольное свойство, например, тип (источник примера): selector = {
type(x) == str : "it's a str",
type(x) == tuple: "it's a tuple",
type(x) == dict : "it's a dict"
}[1] # можно использовать число 1 как синоним True
При необходимости более сложных действий хранить функцию в качестве значения по каждому ключу: import operator operations = {
'+': operator.add,
'*': lambda x, y: x * y,
# ...
} def calc(operation, a, b):
return operations[operation](a, b)
Будьте внимательны при использовании словаря с функциями — убедитесь, что вы не вызываете эти функции внутри словаря, а передаёте по ключу; иначе все функции будут выполняться каждый раз при конструировании словаря.
Другие способы Приведены скорее для ознакомления, чем для реального использования.
Использование функций с шаблонными именами Создадим класс, в котором напишем несколько методов вида: def process_first(self):
... def process_second(self):
... ...
И один метод-диспетчер: def dispatch(self, value):
method_name = 'process_' + str(value)
method = getattr(self, method_name)
return method()
После этого можно использовать метод dispatch для выполнения соответствующей функции, передавая её суффикс, например x.dispatch('first')
Использование специальных классов Если есть желание использовать синтаксис switch-case в максимально похожем стиле, можно написать что-то вроде следующего кода class switch(object):
def __init__(self, value):
self.value = value # значение, которое будем искать
self.fall = False # для пустых case блоков def __iter__(self): # для использования в цикле for
""" Возвращает один раз метод match и завершается """
yield self.match
raise StopIteration def match(self, *args):
""" Указывает, нужно ли заходить в тестовый вариант """
if self.fall or not args:
# пустой список аргументов означает последний блок case
# fall означает, что ранее сработало условие и нужно заходить
# в каждый case до первого break
return True
elif self.value in args:
self.fall = True
return True
return False
Используется следующим образом: x = int(input()) for case in switch(x):
if case(1): pass
if case(2): pass
if case(3):
print('Число от 1 до 3')
break
if case(4):
print('Число 4')
if case(): # default
print('Другое число')
Использование операторов and и or Довольно небезопасный способ, см. пример: # Условная конструкция Select Case x
Case x<0 : y = -1
Case 0<=x<1 : y = 0
Case 1<=x<2 : y = 1
Case 2<=x<3 : y = 2
Case Else : y = 'n/a'
End Select # Эквивалентная реализация на Python y = (( x < 0 and 'first segment') or
(0 <= x < 1 and 'second segment') or
(1 <= x < 2 and 'third segment') or
(2 <= x < 3 and 'fourth segment') or 'other segment')
Этот способ использует короткую схему вычисления операторов and и or, т.е. то, что логические выражения вычисляются следующим образом: (t вычисляющееся как True, f вычисляется как False): f and x = f
t and x = x
f or x = x
t or x = t
Предложенный способ вычислений будет работать только в том случае, если второй аргумент оператора and всегда будет содержать True-выражение, иначе этот блок всегда будет пропускаться. Например: y = (( x < 0 and -1) or
(0 <= x < 1 and 0) or
(1 <= x < 2 and 1) or
(2 <= x < 3 and 2) or 'n/a')
Будет работать неправильно в случае 0 <= x < 1, т.к. выражение 0 <= x < 1 and 0 равно 0, и из-за этого управление перейдёт следующему аргументу or, вместо того, чтобы вернуть этот ноль в качестве результата выражения.
Использование исключений Если объявить несколько функций: import sys class case_selector(Exception):
def __init__(self, value): # один обязательный аргумент
Exception.__init__(self, value) def switch(variable):
raise case_selector(variable) def case(value):
exc_сlass, exс_obj, _ = sys.exc_info()
if exc_сlass is case_selector and exс_obj.args[0] == value:
return exс_class
return None
Здесь используется функция sys.exc_info, которая возвращает набор из информации об обрабатываемом исключении: класса, экземпляра и стека. Код с использованием этих конструкций будет выглядеть следующим образом: n = int(input())
try:
switch(n)
except ( case(1), case(2), case(3) ):
print "Число от 1 до 3"
except case(4):
print "Число 4"
except:
print "Другое число"
Рекомендуемая литература и документация по знаниям и навыкам разработки, не привязанным к конкретным языкам и платформам. Все эти книги следовало бы добавить в каждый список литературы, но мы следуем принципу DRY и сохраняем их здесь, в одном месте. Дополнения к ответу всячески приветствуются!
Данный перечень входит в поддерживаемый сообществом Сборник учебных ресурсов по программированию
Ответ
Архитектура ПО
Structure and Interpretation of Computer Programs - 2nd Edition. Harold Abelson, Gerald Jay Sussman, Julie Sussman Русский перевод: Структура и Интерпретация Компьютерных Программ. Харольд Абельсон, Джеральд Джей Сассман
Алгоритмы и структуры данных
The Algorithm Design Manual. Steven S Skiena. 2008 Русский перевод: Алгоритмы. Руководство по разработке. Стивен Скиена. 2014 г.
Introduction to Algorithms, 3rd Edition. Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, Clifford Stein. 2009 Русский перевод: Алгоритмы. Построение и анализ. Томас Х. Кормен, Чарльз И. Лейзерсон, Рональд Л. Ривест, Клиффорд Штайн. 2015 г.
The Art of Computer Programming. Donald E. Knuth. 2011 Русский перевод: Искусство программирования. Дональд Э. Кнут. 2015 г.
Algorithms + Data Structures = Programs. Niklaus Wirth, 1976 (or Algorithms + Data Structures. 2004. Русский перевод: Алгоритмы + структуры данных = программы. М.: Мир, 1985, Алгоритмы и структуры данных. М.: Мир, 1989, Алгоритмы и структуры данных. Новая версия для Оберона. М.: ДМК Пресс, 2010. Старая версия книги, в отличие от новых, содержит подробно разобранный компилятор простого языка.
Проектирование и стиль кода
Design Patterns: Elements of Reusable Object-Oriented Software. Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides Русский перевод: Приёмы объектно-ориентированного проектирования. Паттерны проектирования. Эрих Гамма, Ричард Хелм, Ральф Джонсон, Джон Влиссидс
Patterns of Enterprise Application Architecture. Martin Fowler Русский перевод: Архитектура корпоративных программных приложений. Мартин Фаулер
Domain-Driven Design: Tackling Complexity in the Heart of Software. Eric Evans Русский перевод: Предметно-ориентированное проектирование. Структуризация сложных программных систем. Эрик Эванс
«Совершенный код» (Code Complete). Стив Макконнелл
«Рефакторинг. Улучшение существующего кода». Мартин Фаулер
«Чистый код. Создание, анализ и рефакторинг» (Clean Code: A Handbook of Agile Software Craftsmanship). Роберт Мартин
Навыки разработчика
Эндрю Хант, Дэвид Томас — «Программист-прагматик. Путь от подмастерья к мастеру»
Организация процесса разработки
Том ДеМарко — «Deadline. Роман об управлении проектами»
Роберт Мартин — «Быстрая разработка программного обеспечения»
Джоэл Спольски — «И снова о программировании».
Джо Мараско — «IT-проекты. Фронтовые очерки»
Стив Макконнелл — «Сколько стоит программный проект»
Джин Ким, Кевин Бер, Джордж Спаффорд — «Проект «Феникс». Роман о том, как DevOps меняет бизнес к лучшему».
Синтаксический разбор и компиляция
Compilers: Principles, Techniques, and Tools, Alfred V. Aho, Monica S. Lam, Ravi Sethi, Jeffrey D. Ullman Русский перевод: Компиляторы: принципы, технологии и инструментарий, Альфред Ахо, Моника С. Лам, Рави Сети, Джеффри Ульман. Известна как «Книга Дракона».
Навеяно статьёй о различиях DTO, POCO и Value Object на Хабрахабре:
DTO vs POCO vs Value Object, а также
вопросом POCO vs DTO Нигде нет конкретных примеров. Приведите, пожалуйста, конкретный пример с небольшим описанием (или также примером), где и как его использовать и для чего. UPD Отличные ответы. Всем спасибо. Еще небольшой вопрос по использованию POCO. Когда и насколько рационально запихивать логику в объекты? Вот к примеру, у меня есть сервисный слой, который возвращает POCO, какие именно методы я туда могу вставить? Допустим, мне нужно валидировать Кастомера, ок, я сделал в POCO метод Validate, пока мне не нужно лезть для валидации в базу - все хорошо, но как только это понадобиться, идея уже не кажется такой хорошей. Или я не прав? Сейчас у меня приложение, где почти все действия выполняет бизнес слой, в моделях только простые методы типа GetFullName, и по сути я оперирую DTO-хами. Так вот, как уследить ту тонкую грань "что в POCO, что в сервисе" или вообще "всю логику в сервисы, оперировать DTO"?
Ответ
Представим некоторый интернет магазин. У этого магазина есть веб-интерфейс и сервер приложений, который обрабатывает логику. Некий пользователь хочет совершить какой-нибудь заказ. Для этого ему нужно выполнить ряд действий: добавить нужные товары в корзину и подтвердить заказ. Для того, чтобы это сделать, на сервере приложений может существовать класс Order public class Order
{
private ItemDiscountService _itemDiscountService;
private UserService _userService; public Order(ItemDiscountService itemDiscountService, UserService userService)
{
_itemDiscountService = itemDiscountService;
_userService = userService
} public int Id { get; set; }
public List- Items { get; set; }
public decimal Subtotal { get;set; }
public decimal Discount { get; set; }
public void AddItem(Item item)
{
Items.Add(item);
CalculateSubtotalAndDiscount();
} public void CalculateSubtotalAndDiscount()
{
decimal subtotal = 0;
decimal discount = 0;
foreach (var item in Items)
{
var currentCost = item.Cost * _itemDiscountService.GetDiscountFactor(item) * _userService.GetCurrentUserDiscountFactor();
subtotal += currentCost;
discount += item.Cost - currentCost;
} Subtotal = subtotal;
Discount = discount;
}
}
Этот класс содержит в себе данные и логику их изменения. Он не унаследован от какого-либо специфического класса из сторонней библиотеки или от какого-либо стороннего класса и является достаточно простым - Plain Old CLR/Java Object
Когда пользователь добавляет что-то в корзину, эта информация передаётся на сервер приложений, что вызывает метод AddItem в классе Order, который пересчитывает стоимость товаров и скидку, меняя тем самым состояние заказа. Нужно отобразить пользователю это изменение, и для этого нужно передать обновлённое состояние обратно на клиент. Но мы не можем просто передать экземпляр нашего Order или его копию, так как он зависит от других классов (ItemDiscountService, UserService), которые в свою очередь могут зависеть от других классов, которым может быть нужно соединение с базой данных и т. п. Конечно, их можно продублировать на клиенте, но тогда на клиенте будет доступна вся наша логика, строка подключения к БД и т. п., чего мы показывать совершенно не хотим. Поэтому, чтобы просто передать обновленное состояние, мы можем сделать для этого специальный класс: public class OrderDto
{
public int Id { get; set; }
public decimal Subtotal { get; set; }
public decimal Discount { get; set; }
public decimal Total { get; set; }
}
Мы сможем поместить в него те данные, которые хотим передать на клиент, создав тем самым Data Transfer Object. В нем могут содержаться совершенно любые нужные нам атрибуты. В том числе и те, которых нет в классе Order, например, атрибут Total
У каждого заказа есть свой идентификатор - Id, который мы используем для того, чтобы отличать один заказ от другого. В то время как в памяти сервера приложений может существовать заказ с Id=1, содержащий в себе 3 предмета, в БД может хранится такой же заказ, с тем же идентификатором, но содержащий в себе 5 предметов. Такое может возникнуть, если мы прочитали состояние заказа из БД и поменяли его в памяти, не сохранив изменения в БД. Получается, что несмотря на то, что некоторые значения у заказа в БД и заказа в памяти сервера приложений будут отличаться, это все равно будет один и тот же объект, так как их идентификаторы совпадают. В свою очередь значение стоимости - 100, номер идентификатора - 1, текущая дата, имя текущего пользователя - "Петрович" будут равны аналогичным значениям только тогда, когда эти значения будут полностью совпадать, и никак иначе. Т. е. 100 может быть равно только 100, "Петрович" может быть равен только "Петрович" и т. д. И неважно, где будут созданы эти объекты. Если их значения будут полностью совпадать - они будут равны. Такие объекты называются Value Object Помимо уже существующих Value Object типа decimal или string можно создавать и свои. В нашем примере мы могли бы создать тип OrderPrice и поместить туда поля Subtotal, Total и Discount. public struct OrderPrice
{
public decimal Subtotal;
public decimal Discount;
public decimal Total;
}
В c# есть подходящая для этого возможность создавать значимые типы которые сравниваются по значению и при присваивании целиком копируются.
UPDATE
Что касается обновленного вопроса (хоть это действительно отдельный большой вопрос, как заметил Discord): Когда мы разрабатываем приложение мы работаем с какой-либо предметной областью. Эта предметная область может быть выражена в виде некоторой модели и действий, которые меняют состояние этой модели. Все это может быть представлено в виде набора классов. Такие классы содержат в себе как данные (в виде полей классов) так и действия, которые этими данными манипулируют (в виде методов). В принципе нет никаких ограничений на размещение данных или методов по классам. Можно вообще все засунуть в один класс и это будет прекрасно работать.
Основная проблема заключается в том, что такой код будет сложнее, а значит дороже поддерживать. Так как все будет переплетено между собой - любые изменения могут привносить кучу ошибок и т.п. Поэтому, для достижения более "дешевого" кода мы начинаем его как-то структурировать, разбивать на модули и т.п. Мы можем разложить данные в одни классы, а методы в другие и это тоже будет работать и будет даже более модульно. Но все равно может нести ряд минусов. Глядя на кучу данных может быть не очевидным то, что вообще с ними может происходить или кому они могут быть нужны. Тоже самое и с кучей методов. Поэтому, чтобы было еще удобнее можно разложить данные по классам как-то сгруппировав их понятным образом. Тоже самое и с методами. Данные заказа, пользователя, товара и т.п. могут стать отдельными классами так же как и классы с соответствующими методами. Это будет еще модульнее и понятнее. Но у любого подхода есть свои плюсы и минусы. Например, в нашем интернет магазине есть различные товары, логика расчета цены которых может быть достаточно сложной.
Представим, что есть некий базовый класс Item, и множество производных классов: public class Item
{
public int Id {get;set;}
public string Name {get;set;}
public decimal BaseCost {get;set;}
public decimal Cost {get;set;}
} public class Boots : Item { ... }
public class Shirt : Item { ... }
public class Pants : Item { ... }
Так как логика у нас находится в отдельных классах, представим что есть класс ItemCostService, который умеет рассчитывать стоимость товара. Тогда,
из-за наличия большого числа различных условий он может выглядеть как-то так: public class ItemCostService
{
public decimal CalculateCost(Item item)
{
if(item is Boots)
{
item.Cost = ...
}
else if (item is Shirt)
{
item.Cost = ...
}
else if ....
}
}
И таких мест в программе, где в зависимости от конкретного типа товара должно быть различное поведение может быть много.
Конечно, это все будет работать. Но, как только у нас появляется новый тип товара, или поменяется логика обработки существующего типа товара нам
придется изменить код в большом количестве мест везде, где присутствуют такие условия. Это сложнее, чем поменять все в одном месте, дольше и чревато тем, что можно что-то забыть сделать. В данном вопросе мы говорим о языках, основной парадигмой которых является ООП. А это значит, что существует готовая инфраструктура которая поддерживает основные принципы ООП. Чтобы следовать этой парадигме и получать выгоду от готовой инфраструктуры мы можем поменять наши класс, добавив логику вычисления стоимости в них, меняя ее по необходимости в производных классах: public class Item
{
...
public virtual void CalculateCost() { ... }
} public class Boots : Item
{
public override void CalculateCost() { ... }
}
Каждый производный тип сам сможет определить логику своего поведения. Вся она будет в одном месте, рядом с данными. А какой из конкретных методов вызвать определит уже инфраструктура избавив нас от этой головной боли. В данном примере такой подход будет более удобен, т.к. у нас пропадет необходимость создавать куче if'ов по всему коду, что только упростит программу и сделает изменения более простыми. Ну и опять же - все зависит от ситуации. Серебряной пули не бывает и в различных случаях стоит использовать различные подходы, которые будут более дешевы в каждой конкретной ситуации. Еще немного про ООП и остальное можете посмотреть в моей статье тут
На данный момент изучаю шаблоны проектирования и пробую применять их на практике, но из-за небольшого опыта работы с ними и отсутствия менторства в этом деле прошу у вас помощи. Есть, например, задача - сделать стрипт для связи с людьми. Подробней:
Мы говорим что хотим связаться с пользователем определенным способом, задаем вспомогательную информацию для объекта, далее говорим отправить. В идеале вижу использование скрипта в виде с использованием фабрики и стратегии: $object = Communication::GetDriver('sms');
$object->setMsg($text);
$object->setTelephone($phone);
$object->send();
Но может потребоваться отправить совершенно другим способом, например, в соцсеть. $object = Communication::GetDriver('socialNetwork');
$object->setMsg($text);
$object->setIdUser($id);
$object->send();
Вот и вопрос, как лучше поступить? Стоит ли заморачиваться с шаблонами? Может быть, следует сделать все это отдельными классами? Это будет оправдано? При этом желательно ловить ошибки и вести лог происходящего. А если использовать шаблоны то как объединить классы? Может быть, есть методы, как сделать лучше?
Ответ
Стоит. Только нужно внести ясность. Обычно полагают, что код пишется "на один раз" или "код слишком простой". На практике часто оказывается, что "да, оно одноразовое, но надо добавить это...и это". В итоге рождается привычка сразу делать структурно - выделить классы, за которыми спрятать лапшу (точек расширения не надо, просто порядок), а в случае веба взять любой фреймворк, а не тешить себя мыслями "да тут делов то ерунда, роутинг простой, шаблоны не надо" Практически всегда есть смысл разделить на логгеры, манагеры, ридеры, чтобы оперировать более высокими уровнями абстракции и спрятать за ними детали, если вы не пишете админские скрипты или не фрилансите (написал, отдал, забыл) Главное не забывать, что шаблоны призваны решать общие проблемы общими способами. Поэтому не должно быть "ооп ради ооп" и "шаблоны ради шаблонов".
Тут многие совершают ошибку - начитаются умных книг и пытаются применить шаблон. Суть в том, что шаблоны - общие решения, которые как то названы. При решении проблем люди приходят к общим решениям, называют его и пишут в книжку. И шаблоны полезны - знание шаблонов может подсказать приемлемые варианты и мозг приучается видеть их сразу. Впрочем, даже если не знать ни одного, то на практике все равно сам их изобретаешь. Также помогают в коммуникации (назвать шаблон проще, чем пояснять, что за структура классов и как она связана).
Но решать то нужно задачи, потому на задачах и нужно акцентироваться, а не на шаблонах. Правильный путь такой: берем правило "делай все проще", добавляем в него "главная сила ооп не в 3 китах, а в гибкости объектов и возможности комбинировать их", приправляем приемами чистого кода и других парадигмами - и решаем задачу. Если видим, что подходит какой то шаблон (обычно не понять какой точно, слишком тонкая грань), то идем к нему решая задачу. Как только задача решена, то все - дальше реализовывать шаблон не нужно. Нет причины - нет кода. Неважно насколько решение получилось "классическим" - любое продолжение есть усложнение. Так что разбираться в шаблонах полезно - это как перенимать чужой опыт решения типичных проблем. И использовать полезно. Но чтобы избежать попадания в ловушку "шаблоны ради шаблонов" следует использовать прием, каким шаблон решает задачу, закодить его и остановиться.
Прошу прощения за картинки-сслыки (не хватает репутации) и за "много букав". О проблеме хочется хотя бы просто поговорить :) Перелопатив с десяток статей я начал осознавать, что ppi - большая палка в колеса. Как с ним бороться, пока не ясно. Итак, у нас есть элементарная страница: jsfiddle.net Мы хотим 14px (к примеру) - как основной размер шрифта. Допустим, что так было в PSD от дизайнера. От этого будем отталкиваться. Обварачиваем весь контент в div.font-size-setting, которому выставляем font-size: 1.4em (так как после нормализации у нас было 10px). Смотрим на страницу с разных устройств:
Будь там iphone4 шрифт и квадратик были бы еще меньше. Проблема, очевидно, в большой разнице в ppi (pixels per inch) устройств. Обычные мониторы имеют 96ppi. У iphone3 163ppi. У виты 220ppi Очевидное решение - это определить ppi устройства и увеличить шрифт. С последним проблем никаких. Обарачиваем в div.font-size-normalize и в зависимости от dpi выставляем соответствующий шрифт: jsfiddle.net Получаем: div.font-size-setting - чтобы выставить размер шрифта от дизайнера, div.font-size-normalize - делаем этот шрифт одинакового размера (визуально) на всех устройствах.
Яблофон чуть растянул сам шрифт или увеличил расстояниями между строчкам, но по сути все сходится. Так или иначе, квадратик с размерами в em везде одинаковый, хоть линейкой меряй. Вопрос в том, как определять ppi? 1.media queries - resolution
Медиа запросы умеют определять не только ширину и высоту, но и много других характеристик, в том числе и dpi (dots per inch). Вообще dpi - бессмысленный параметр для дисплея, так как это параметр для печати принтером, но в нашем случае совпадает с ppi Я решил, что не плохо было бы сделать шаг в 0.1em. Это примерно каждые 10ppi. Взяв наши 96ppi за 1em составил табличку и посчитал диапазон так, чтобы интересующие нас значения были в середине. Составил запросы: @media screen and (min-resolution: 90dpi) and (max-resolution: 100dpi) {
.font-size-normalize { font-size: 1em; /* normal desktops */ }
}
@media screen and (min-resolution: 101dpi) and (max-resolution: 111dpi) {
.font-size-normalize { font-size: 1.1em; }
}
@media screen and (min-resolution: 112dpi) and (max-resolution: 122dpi) {
.font-size-normalize { font-size: 1.2em; }
}
/*...*/
@media screen and (min-resolution: 158dpi) and (max-resolution: 168dpi) {
.font-size-normalize { font-size: 1.7em; /* iphone3 */ }
}
/*...*/
@media screen and (min-resolution: 216dpi) and (max-resolution: 226dpi) {
.font-size-normalize { font-size: 2.3em; /* ps vita */ }
}
/*...*/
@media screen and (min-resolution: 322dpi) and (max-resolution: 332dpi) {
.font-size-normalize { font-size: 3.4em; /* iphone4 */ }
}
http://jsfiddle.net/AHzYJ/3/ Последние версии десктопных браузеров проработали медиазапросы. Браузеры iphone3 и ps vita не осилили. Думаю последние яблофоны, яблопэды, дройдофоны все же справятся, но тем не менее решение далеко не пуленепробиваемо :( 2.media queries - devicePixelRatio и канонический пиксель
dip - density(device)-independent pixel - что то вроде канонического пикселя. Равен одному физическому пикселю на 160ppi. С другой стороны мы имеем параметр devicePixelRatio, который может быть использован как аргумент медиа-запроса относительно канонического пикселя: @media screen and (device-pixel-ratio: 1) {}
Можно написать аналогичное примеру выше множество запросов: @media screen and (min-device-pixel-ratio: 1) and (max-device-pixel-ratio: 1.1) {
.font-size-normalize { font-size: 2.3em; /* ps vita */ }
}
Но у меня ни для одного девайса/компьютера/браузера этого толком сделать не получилось. Такое ощущение, что этот параметр может быть только целочисленным: единица - для iphone3, двойка - для iphone4. Более того, требуются вендерные префиксы, что уже как минимум говорит об отсутвии кросс-браузерности или кросс-девайсности. Еще одна несостыковка в том, что "device-pixel-ratio: 2" (означающий 320ppi) отрабатывает на iphone4, обладающий 326ppi 3.JS alert(window.devicePixelRatio);
Думаю, оно работает точно так же как и второй способ. На iphone3 я увидел "1", так же как в десктопном браузере и на вите, что само собой чушь. На iphone4 высветилось "2", но после предыдущих фэйлов это уже не имеет значение. Еще проблема: тормозилла не справилась и показала undefined 4.Самописные JS-функции, определяющие ppi.
Что-то типа взять device-width и поделить на width. Это работает на мобильных устройствах, так как нельзя сплющить окно браузера, но в десктопном браузере мы можем просто уменьшить размер окна и все сломается. В конечном итоге, я не нашел ни одного полнофункционального решения. Думаю, что просто должно пройти время, пока "device-pixel-ratio: n" будет пониматься всеми. А пока надо придумать как максимизировать количество удачных определений ppi. Ведь описанные выше способы частично, но все же работают. Кто что думает по этому поводу, какие можетe предложить решения и как можно обобщить вышеуказанные способы в один пусть полурабочий вариант?
Ответ
Спустя какое-то время я пришел к тому, что надо работать по второму предложенному мною способу. Суть рабты примерно следующая. Сайт верстается "как обычно", все размеры задаются в пикселях. Только теперь width: 100px не означает ширину в 100 физических пикселей дисплея. Это 100 некоторых абстрахных пикселей (device independent pixel). А дальше операционная система (или браузер) уже сами решают, как показать один такой абстрактный пиксель на дисплее. Помогает им в этом параметр devicePixelRatio. Если этот параметр равен единице, то абстрактный пиксель показывается один в один с физическим пикселем. Если равен двойке, то используется квадрат из четырех пикселей.
Если мы возьмем случайный сайт, вообще не оптимизированный под дисплеи с высокой плотностью пикселя, то он никак не сожмется на ретине. Он будет выгялдеть как на обычном дисплее.
Получается, что за размерами следить не нужно. Ширины, высоты, бордюры, тени и прочее можно задавать в обычных пикселях. И не беспокоится о том, что же там будет на ретине. А вот над графикой придется попотеть. Картинки на девайсах с высокой плостностью пикселя выглядят растянутыми. Их надо заменять на картинки увеличенные в соответствующее количество раз. Например.
div {
background: url(cake.png) no-repeat center top;
} @media screen and (-webkit-min-device-pixel-ratio: 2),
screen and ( -moz-min-device-pixel-ratio: 2),
screen and ( -o-min-device-pixel-ratio: 2),
screen and ( min-device-pixel-ratio: 2) {
div {
background-image: url(cake@2x.png);
background-size: 100%; // или вариации
}
}
Иногда при запуске своего приложения я получаю подобную ошибку: Exception in thread "main" java.lang.NullPointerException
at com.example.myproject.Book.getTitle(Book.java:16)
at com.example.myproject.Author.getBookTitles(Author.java:25)
at com.example.myproject.Bootstrap.main(Bootstrap.java:14)
Мне сказали, что это называется «трассировкой стека» или «stack trace». Что такое трассировка? Какую полезную информацию об ошибке в разрабатываемой программе она содержит?
Немного по существу: довольно часто я вижу вопросы, в которых начинающие разработчики, получая ошибку, просто берут трассировки стека и какой-либо случайный фрагмент кода без понимания, что собой представляет трассировка и как с ней работать. Данный вопрос предназначен специально для начинающих разработчиков, которым может понадобиться помощь в понимании ценности трассировки стека вызовов. Перевод вопроса: «What is a stack trace, and how can I use it to debug my application errors?» @Rob Hruska
Ответ
Простыми словами, трассировка стека – это список методов, которые были вызваны до момента, когда в приложении произошло исключение. Простой случай В указанном примере мы можем точно определить, когда именно произошло исключение. Рассмотрим трассировку стека: Exception in thread "main" java.lang.NullPointerException
at com.example.myproject.Book.getTitle(Book.java:16)
at com.example.myproject.Author.getBookTitles(Author.java:25)
at com.example.myproject.Bootstrap.main(Bootstrap.java:14)
Это пример очень простой трассировки. Если пойти по списку строк вида «at…» с самого начала, мы можем понять, где произошла ошибка. Мы смотрим на верхний вызов функции. В нашем случае, это: at com.example.myproject.Book.getTitle(Book.java:16)
Для отладки этого фрагмента открываем Book.java и смотрим, что находится на строке 16 public String getTitle() {
System.out.println(title.toString()); <-- line 16
return title;
}
Это означает то, что в приведенном фрагменте кода какая-то переменная (вероятно, title) имеет значение null Пример цепочки исключений Иногда приложения перехватывают исключение и выбрасывают его в виде другого исключения. Обычно это выглядит так: try {
....
} catch (NullPointerException e) {
throw new IllegalStateException("A book has a null property", e)
}
Трассировка в этом случае может иметь следующий вид: Exception in thread "main" java.lang.IllegalStateException: A book has a null property
at com.example.myproject.Author.getBookIds(Author.java:38)
at com.example.myproject.Bootstrap.main(Bootstrap.java:14)
Caused by: java.lang.NullPointerException
at com.example.myproject.Book.getId(Book.java:22)
at com.example.myproject.Author.getBookIds(Author.java:35)
... 1 more
В этом случае разница состоит в атрибуте "Caused by" («Чем вызвано»). Иногда исключения могут иметь несколько секций "Caused by". Обычно необходимо найти исходную причину, которой оказывается в самой последней (нижней) секции "Caused by" трассировки. В нашем случае, это: Caused by: java.lang.NullPointerException <-- root cause
at com.example.myproject.Book.getId(Book.java:22) <-- important line
Аналогично, при подобном исключении необходимо обратиться к строке 22 книги Book.java, чтобы узнать, что вызвало данное исключение – NullPointerException Еще один пугающий пример с библиотечным кодом Как правило, трассировка имеет гораздо более сложный вид, чем в рассмотренных выше случаях. Приведу пример (длинная трассировка, демонстрирующая несколько уровней цепочек исключений): javax.servlet.ServletException: Произошло что–то ужасное
at com.example.myproject.OpenSessionInViewFilter.doFilter(OpenSessionInViewFilter.java:60)
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
at com.example.myproject.ExceptionHandlerFilter.doFilter(ExceptionHandlerFilter.java:28)
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
at com.example.myproject.OutputBufferFilter.doFilter(OutputBufferFilter.java:33)
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:388)
at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:182)
at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:765)
at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:418)
at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
at org.mortbay.jetty.Server.handle(Server.java:326)
at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:542)
at org.mortbay.jetty.HttpConnection$RequestHandler.content(HttpConnection.java:943)
at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:756)
at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:218)
at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:404)
at org.mortbay.jetty.bio.SocketConnector$Connection.run(SocketConnector.java:228)
at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:582)
Caused by: com.example.myproject.MyProjectServletException
at com.example.myproject.MyServlet.doPost(MyServlet.java:169)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)
at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:511)
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1166)
at com.example.myproject.OpenSessionInViewFilter.doFilter(OpenSessionInViewFilter.java:30)
... 27 more
Caused by: org.hibernate.exception.ConstraintViolationException: could not insert: [com.example.myproject.MyEntity]
at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:96)
at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)
at org.hibernate.id.insert.AbstractSelectingDelegate.performInsert(AbstractSelectingDelegate.java:64)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2329)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2822)
at org.hibernate.action.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:71)
at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:268)
at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:321)
at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:204)
at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:130)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:210)
at org.hibernate.event.def.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:56)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:195)
at org.hibernate.event.def.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:50)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:93)
at org.hibernate.impl.SessionImpl.fireSave(SessionImpl.java:705)
at org.hibernate.impl.SessionImpl.save(SessionImpl.java:693)
at org.hibernate.impl.SessionImpl.save(SessionImpl.java:689)
at sun.reflect.GeneratedMethodAccessor5.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.hibernate.context.ThreadLocalSessionContext$TransactionProtectionWrapper.invoke(ThreadLocalSessionContext.java:344)
at $Proxy19.save(Unknown Source)
at com.example.myproject.MyEntityService.save(MyEntityService.java:59) <-- relevant call (see notes below)
at com.example.myproject.MyServlet.doPost(MyServlet.java:164)
... 32 more
Caused by: java.sql.SQLException: Violation of unique constraint MY_ENTITY_UK_1: duplicate value(s) for column(s) MY_COLUMN in statement [...]
at org.hsqldb.jdbc.Util.throwError(Unknown Source)
at org.hsqldb.jdbc.jdbcPreparedStatement.executeUpdate(Unknown Source)
at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeUpdate(NewProxyPreparedStatement.java:105)
at org.hibernate.id.insert.AbstractSelectingDelegate.performInsert(AbstractSelectingDelegate.java:57)
... 54 more
В этом примере приведен далеко не полный стек вызовов. Что вызывает здесь наибольший интерес, так это поиск функций из нашего кода – из пакета com.example.myproject. В предыдущем примере мы сначала хотели отыскать «первопричину», а именно: Caused by: java.sql.SQLException
Однако все вызовы методов в данном случае относятся к библиотечному коду. Поэтому мы перейдем к предыдущей секции «Caused by» и найдем первый вызов метода из нашего кода, а именно: at com.example.myproject.MyEntityService.save(MyEntityService.java:59)
Аналогично предыдущим примерам, необходимо обратить внимание на MyEntityService.java, строка 59: именно здесь появилась ошибка (в данном случае ситуация довольно очевидная, так как об ошибке сообщает SQLException, но в этом вопросе мы рассматриваем именно процедуру отладки с помощью трассировки). Перевод ответа: «What is a stack trace, and how can I use it to debug my application errors?» @Rob Hruska
Как известно, сборщик мусора в C# (точнее, в CLR) время от времени проводит чистку оперативной памяти, освобождая память, занятую переменными, которые больше не используются. Кроме этого он также производит дефрагментацию памяти, "уплотняя" кучу. В связи с этим происходит коррекция ссылок на объекты, пережившие сборку мусора. Вероятно, что-то аналогичное происходит при сборке мусора и в других языках. В С++ нет сборщика мусора. В таком случае, даже если программист не забудет очистить всю память, выделенную ранее, то ее все равно может оказаться недостаточно из-за фрагментации, так как процесс дефрагментации не проводится. То есть возможна парадоксальная ситуация, когда общий размер свободной памяти больше, чем требуется для создания нового объекта, но объект не может быть создан.
Так ли это? Есть ощущение, что я ошибаюсь в своих рассуждениях, но где?
Ответ
Вопрос очень хороший. И тема интересная и важная. Однако, мне кажется (может быть, просто кажется), что Вы путаете две вещи, точнее, два уровня фрагментации памяти. Память может быть фрагментирована на уровне физической памяти. В системах с виртуальной моделью памяти (а таких сейчас подавляющее большинство) это не проблема, так как даже сильно фрагментированная реальная память будет просто спроецирована на последовательное виртуальное адресное пространство процесса.
Другое дело, если фрагментация происходит на уровне виртуальной памяти. Это может запросто произойти в программах на С или С++, где происходит многочисленные выделения и удаления небольших фрагментов памяти. Это может привести к сильной утечки памяти (хотя в коде вся выделенная память освобождается!) и, возможно, к исчерпанию всей системной памяти. Но тут уже всему настанет кердык, если система такие ситуации не отслеживает и не выгружает "прожорливые" процессы.
Хочу научиться писать тесты для своих проектов. Подскажите какие-нибудь хорошие ресурсы, чтобы научиться тестировать Android приложения. Насколько важно их использование? Сейчас я пишу приложение без их использования и пока не могу оценить их пользу. Те коды, которые встречаю в интернете, не помню, чтобы где-то в коде встречал тесты. В общем, хочу понять, что это значит. Подскажите, с чего начать. Допустим есть вот такой метод: private File getFile(File path) {
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
return new File(path.getPath() + File.separator + timeStamp + ".html");
}
Как можно к нему написать тест и что нужно для этого сделать? ПРАВКА Вот кстати есть ссылка с видео где показан пример теста https://www.youtube.com/watch?v=ZJE0MDKJOow
Ответ
Для того чтобы создать юнит тест, вам прежде всего нужно определиться что вы собственно собираетесь тестировать. В идеале, ваш метод должен делать что-то одно и тогда ваша задача упрощается. Если возможно, то юнит тест должен тестировать метод как черный ящик, то есть, вы подаете что-то на вход и проверяете полученное значение. К сожалению это не всегда возможно. В вашем конкретном случае, первое что бросается в глаза, это то что метод помечен как private, такой метод нельзя тестировать стандартными методами. Существует много теорий насчет того нужно ли тестировать приватные методы или нет. Мое мнение - их можно не тестировать. Правильность работы приватных методов будет проверена неявно когда вы тестируете все открытые методы. Хотя если вы очень хотите то можно пометить метод как 'default' (убрать тип доступa), или можно использовать reflection. Но допустим ваш метод публичный, тогда анализируя код понимаем, что метод возвращает новый файл с именем построенным из пути и функции текущей даты. То есть, он делает два действия:
* Создает имя файла
* Создает собственно файл
Тестировать его в текущем виде можно но не интересно. Я бы посоветовал немного поправить ваш код чтобы он был более пригодным для тестирования: public File getFileFullName(File path, Date date) {
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String fullPath = path.getPath() + File.separator + timeStamp + ".html";
return fullPath;
} // где-то в вашем коде
String path = ...
Date now = new Date();
String fileFullName = getFileFullName(path, now);
File file = new File(fileFullName);
// тест для метода getFileFullName в классе ClassUnderTest
@Test
public void testGetFileFullName() {
String path = "/abc";
Date date = new Date(1465953124); //2016-06-15 13:12:06 ClassUnderTest instance = new ClassUnderTest()
String name = instance.getFileFullName(path, date); assertEquals("/abc/20160615_131206.html");
}
Но вы можете спросить, а как тестировать код где мы создаем дату или используем файл? Касательно даты, я бы рекомендовал вообще не использовать new Date() в коде. Вместо этого использовать что типа провайдера или сервиса для получения текущей даты/времени. Тогда вы можете подменять реализацию этого класса для тестирования. Простейшая реализация может быть синглтон: public class DateTimeUtils { private static DateTime fixedTime; public static DateTime getCurrentDateTime() {
if (fixedTime == null) {
return new DateTime(DateTimeZone.UTC);
}
return fixedTime;
}
public static void useFixedCurrentTime(DateTime timeToReturn) {
fixedTime = timeToReturn;
}
}
Что касается объекта File то здесь вам поможет подмена классов с помощью мокирования. Посмотрите Mockito или PowerMock. Еще маленькое дополнение. Если бы вы использовали test-driven development то такой проблемы бы не было.
Что такое interning? Для чего оно применяется? Когда стоит его применять и какие возможны подводные камни?
Ответ
Interning — это метод хранения лишь одной копии из многих одинаковых объектов. Применяется в C# и Java к строкам, а также (в Java) к небольшим числам. Рассмотрим на примере строк. Когда вы говорите string.Intern(s) в C# или s.intern() в Java для строки s, вы получаете строку с таким же содержимым, но возвращённая строка гарантировано одна и та же (то есть, один и тот же объект), если вы запрашиваете интернированную строку с одним и тем же содержимым. Также, строковые константы автоматически интернируются. Однако, строки полученные другим путём, например, через StringBuilder или конкатенацию, не будут интернированы, по крайней мере в текущей версии языков. (Впрочем, оптимизатор может соптимизировать конкатенацию, если сумеет вычислить аргументы во время компиляции, так что рассчитывать на это не стоит.) Пример: // C#
object.ReferenceEquals("123", "123") // true
object.ReferenceEquals(string.Intern("12" + "3"), "123") // true
char[] chars = new[] { '1', '2', '3' };
object.ReferenceEquals(new string(chars), new string(chars)) // false
object.ReferenceEquals(new string(chars), "123") // false
object.ReferenceEquals(string.Intern(new string(chars)), "123") // true // Java
"123" == "123" // true
("12" + "3").intern() == "123" // true
new String("123") == new String("123") // false
new String("123") == "123" // false
new String("123").intern() == "123" // true
Это значит, что интернированные объекты можно сравнивать через ReferenceEquals (C#) / == (Java). Когда вызывается метод Intern()/intern(), рантайм-библиотека просматривает пул интернированных объектов в поисках данного или равного ему. Если такой объект находится, он возвращается, если нет, данный объект интернируется и возвращается.
Для чего можно пользоваться этим? Например, можно уменьшить расход памяти программы, если в ней используется большое количество строк, среди которых много дубликатов. Например, у вас есть огромный XML-файл, состоящий из почти одинаковых записей. Или огромный текст программы на каком-нибудь языке программирования. Тогда в некоторых случаях можно уменьшить потребление памяти путём интернирования строк: например, все экземпляры while будут одним и тем же объектом. Внимание! Сама по себе считанная из файла строка не интернируется, даже если она и равна какой-то интернированной строке. Учтите, однако, что однажды интернированую строку нельзя «деинтернировать», и она будет занимать память программы даже когда больше не будет вам нужна. Поэтому имейте в виду, что интернирование строк может оказать и негативный эффект на расход памяти программой! Поэтому если вы решаете применить интернирование в своей программе, обязательно спрофилируйте расход памяти и убедитесь, что ваша оптимизация действительно улучшает ситуацию! (Впрочем, это относится практически ко всем оптимизациям.) Далее, интернирование строки делает поиск в глобальных структурах, и поэтому наверняка будет требовать глобальной блокировки. Поэтому несколько потоков, активно применяющих интернирование, будут «сражаться» за общий ресурс. Ещё одним преимуществом интернированных строк является то, что их можно быстрее сравнивать. Например, если вы разбираете программный текст, и все ключевые слова интернированы, вы можете сравнивать их как объекты (что, разумеется, намного скорее).
В .NET вы можете управлять тем, будет ли применяться автоматическое интернирование строковых констант на уровне сборок (assembly). По умолчанию строковые константы, как было сказано выше, интернируются, но вы можете запретить это, указав атрибут CompilationRelaxations.NoStringInterning
В Java кроме строк интернируются также и упакованные (boxed) числа. Например, упакованные константы типов Integer и Long в пределах от -128 до 127, Boolean и Byte хранятся в пуле интернированных объектов. Пример: Integer x = 1;
Integer y = 1;
Integer z = new Integer(1); x == y // true
y == z // false
Часто вижу в различной литературе, видеоуроках, статьях в интернете и прочем, что демонстративные функции и методы носят название foo. Что это значит ?
Ответ
Скорее всего слова foo, bar и baz родились в комиксе Smokey Stover and Pogo в конце 30-х годов 20-го века (как правильно заметил AnT) и, благодаря своей популярности, стали использоваться технарями из MIT От автора комикса
What’s Foo? My uncle found this word engraved on the bottom of a jade
statue in San Francisco’s China town. The word Foo means Good-Luck.
Что такое Foo? Мой дядя нашел это слово выгравированным на дне нефритовой статуэтки в «China town» в Сан Франциско. И обозначает (переводится) оно «удача» или «удачи!»
Вот очень подробный и развернутый ответ на этот вопрос в английской версии StackOverflow. В этом ответе есть воспоминания людей об употреблении слов foobarbazfoobar , которые работали непосредственно в Tech Model Railroad Club (сокращенно TRMC) или, несколько позже, просто в кругу MIT в 1960-1990-х годах. Также в популяризации данных слов сыграла роль военная аббревиатура FUBAR («Fucked Up Beyond All Repair», что можно перевести как «ремонту не подлежит», что относилось к военной технике, либо «Fucked Up Beyond All Recognition» — речь шла о людских жертвах, которые невозможно опознать), которая появилась во время второй мировой войны и по слухам была придумана неким рядовым, которого «задолбали» всякие военные аббревиатуры. Технари из TRMC клуба в MIT использовали слово «FOO» для обозначения ситуаций когда была необходима аварийная остановка системы. В случае, когда кто-нибудь нажимал один из аварийных выключателей на системном табло вместо времени появлялась надпись «FOO» и поэтому эти выключатели назвали «Foo switches». Позже в этом клубе стали использовать кнопки с подписями «FOO» и «BAR» (уже как дань традиции), и использовались они в самых разных ситуациях. Впоследствии это стало использоваться в IT мире как «placeholders», то есть для названия переменных/классов в тех случаях, когда это не важно (например в примерах) или когда на ум ничего лучшего не приходит. P.S.: также существует неподтвержденная версия, что «FOOBAR» происходит от немецкого «furchtbar» (ужасно).
|