Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
#error TODO! implement this feature
#warning TODO! implement this feature
// TODO! implement this feature

Connection InitClient(args) {
var socket = CreateSocket(args);
var connection = CreateConnection(socket);
connection.StartHandshake();
}def startServer(): Either[String, Server] = {
for {
conf <- loadConfig().right
sever <- startServer(conf).right
_ <- addListeners(server).right
_ <- addHooks(server).right
_ <- logServerSetup(server).right
_ <- verifyServer(server).right
} yield server
def startServer(conf: ServerConfig) = {
try {
val server = new Server(conf)
server.start()
Right(server)
} catch {
case e: ServerException => Left("Failed to start server " + e)
}
>>многие решения генерируют техническую документацию именно из комментариев к коду.
И это очень плохо. В результате получается документация, которая «как бы есть».
Свернуть комментарии — идея хорошая — но это доп действия ручками.Зачастую в IDE есть и автоматическое сворачивание регионов определённого типа.
if (equation > 0) {
doSmth();
} else if (equation < 0) {
doSmthElse();
} else {
// комментарий о том что случай с равенством нулю не забыт,
// обработка не требуется.
}//fallthrough всегда пишу когда применяю.Подождите, вы пишите код, которым описываете, как вы это сделали
if (equation > 0) {
doSmth();
} else if (equation < 0) {
doSmthElse();
} else {
// комментарий о том что случай с равенством нулю не забыт,
// обработка не требуется.
}var color = $("#color").val().toUpperCase(), cL = color.length;
for(var i in cL)
if(cL[i] < '0' && cL[i] > 'F') //найден не hex-символ
break;
document.body.style.backgroundColor = (color.charAt[0] !='#' && i < cL ?'':'#') + color;
if(cL[i] < '0' && cL[i] > 'F') //найден не hex-символCharIsHexSymbol(c) {
return c > '0' && c < 'F';
}
выносить, скажем так, «одноразовый» код в отдельные методы так же не лучшая практика.
А чтобы с комментариями никто ничего не испортил, тут уж точно надо уметь читать мысли — причём из будущего. Чтобы понять, что придёт в голову будущему редактору, что для него будет очевидным, а что — тоже очевидным, но при этом неправильным?
n.z = sqrt(saturate(1.0f - dot(n.xy,n.xy)));
float4 q2 = q0 * (2.0f / dot(q0,q0));
q0 *= scale;
float4 qq0 = q0 * q2 * 0.5f;
float3 q0x = q0.xxx * q2.yzw;
float3 q0y = q0.yyz * q2.zww;
float3 q1x = q1.xxx * q2.wzy;
float3 q1y = q1.yyy * q2.zwx;
float3 q1z = q1.zzz * q2.yxw;
float3 q1w = q1.www * q2.xyz;
float4 row_0 = float4(qq0.w + qq0.x - qq0.y - qq0.z,q0x.x - q0y.z,q0x.y + q0y.y,q1w.x + q1x.x - q1y.x + q1z.x);
float4 row_1 = float4(q0x.x + q0y.z,qq0.w + qq0.y - qq0.x - qq0.z,q0y.x - q0x.z,q1w.y + q1x.y + q1y.y - q1z.y);
float4 row_2 = float4(q0x.y - q0y.y,q0x.z + q0y.x,qq0.w + qq0.z - qq0.x - qq0.y,q1w.z - q1x.z + q1y.z + q1z.z);
А если вы такую ситуацию не можете воспроизвести, то может быть и не стоит так код писать.
Но, насколько я понял, в данном топике обсуждаются именно комментарии к коду, а не аннотации, которые несомненно полезны.
Вынос куска кода, который используется один раз, не требуется по DRY. Но и не нарушает этот принцип.
Работа программиста – это работа переводчика.Понравилась аналогия, но она скорее защищает комментарии. Хороший переводчик с помощью примечаний (комментариев) передает не только смысл, но и контекст, которым читатель вероятно не обладает. Культурный, философский, юридический или ещё какой — не суть. Первый пришедший в голову пример — в английском есть слово «sheriff», которого в русском нет (ну или не было пускай даже в пассивном словаре значительной части, если не большинства, русскоговорящих относительно недавно), переводчик может писать «глава полицейского управления округа в США (как правило выборная должность)» каждый раз, может слово «заимствовать», не раскрывая понятия («формально в русском языке слово давно есть кому надо посмотрят в БСЭ»), а может дополнительно дать ему один раз определение в примечаниях, да ещё желательно с фразой «здесь», поскольку у слова «шериф» несколько значений и в контексте современных США оно означает несколько другую сущность, чем в контексте средневековой Англии.
Моя идея в том — что всю информацию можно передать кодом.
// // Далее мы ищем сумму пирожков во всех корзинках. Массив a[] содержит число пирожков в каждой корзинке
int c = a.Length();
int s = 0;
for (int i = 0; i < c; i++)
{
s += a[i];
}
// Далее мы ищем cумму пирожков во всех корзинках. Массив pattyCountsInBaskets[] содержит число пирожков в каждой корзинке
int pattySum = 0;
for (int i = 0; i < pattyCountsInBaskets.Length(); i++)
{
pattySum += pattyCountsInBaskets[i];
}
// Далее мы ищем cумму пирожков во всех корзинках.
int pattySum = 0;
for (int i = 0; i < pattyCountsInBaskets.Length(); i++)
{
pattySum += pattyCountsInBaskets[i];
}
int pattySum = 0;
foreach (int puttyCount in pattyCountsInBaskets)
{
pattySum += puttyCount;
}
int pattySum = pattyCountsInBaskets.Sum();
int pattySum = Sum(pattyCountsInBaskets);
int pattySum = pattyCountsInBasketsSum();
for (int i = 0; i < UBOUND_1; i++) // i - Month Dimension, UBOUND_1 - Upper Bound of Month Dimension
{
for (int j = 0; j < UBOUND_2; j++) // j - ......., UBOUND_2 - .......
{
for (int k = 0; k < UBOUND_3; k++) // k - ......, UBOUND_3 - ......
{
if (k > 3)
{
s += a[i, j, k];
}
}
}
}
for (int monthIndex = 0; monthIndex < monthCount; monthIndex++) // monthIndex - Month Dimension, monthCount - Upper Bound of Month Dimension
enum Month: int
{
January = 0,
February = 1,
March 2,
.....
}
foreach (var month in Enum.GetValues(typeof(Month))
m1 = F / (G * (m2 / pow(R, 2)) // Используем следствие закона всеобщего тяготения НьютонаcalcM1ByConsequenceOfNewtonsLawOfUniversalGravitation чур не предлагать.public class Planet
{
private readonly IPhysics physics;
private readonly double mass;
private readonly double radius;
public Planet (IPhysics physics, double mass, double radius)
{
if (physics == null) throw new AgumentNullException ("physics");
if (mass <= 0) ...
if (radius <= 0) ...
this.physisc = physics;
this.mass = mass;
this.radius = radius;
}
...
public double CalulcateImpulse (double force)
{
return this.physics.CalculateImpulse (force, this.mass, this.radius);
}
}
internal class NewtonPhysics : IPhysics
{
...
public double CalulcateImpulse (double force, double mass, double radius)
{
return force / (G * (mass / Math.Pow(radius, 2));
}
}
planet.CalculateImpulse(1).planet.CalculateImpulse(1)? Мы именно для того пишем программы, чтобы за нас на подобное отвечал компьютер. Вам лишь важно знать, что при вызове planet.CalculateImpulse(1) будет возвращён импульс планеты для силы 1. Если вам нужно проверить, что CalculateImpulse считается верно, то вам нужно будет написать тест для интерисующей вас физики (IPhysics) — в данном случае у нас только одна реализация и нам нужно будет написать тест для NewtonPhysics.CalulcateImpulse. В виду того, что метод Planet.CalulcateImpulse тривиален, его вообще можно не тестировать (при условии, что тест для NewtonPhysics.CalulcateImpulse написан).А скажите мне сначала зачем вам знать что выдаст planet.CalculateImpulse(1)?
Используем следствие закона всеобщего тяготения Ньютона
Ваш вариант комментария:
Но только в нашем случае мы вынесли часть, которая скорее всего будет использована в других расчётах (не только планет) и разъединили классы вычислений и планеты тем самым подготовив их к тестированию и расширению другими физическими теориями.
А это было нужно?
Возможность модификации в нетривиальных программах подразумевает тестируемость.
Код не должен давать понимание, почему мы реализуем именно такое решение.
object_mass = getObjectMass(force, self_mass, distance);
function getObjectMass(force, subject_mass, distance)
{
return getFirstMassUsingNewtonsLawUniversalGravitation(force, subject_mass, distance);
}
function getFirstMassUsingNewtonsLawUniversalGravitation(F, m2, R)
{
return F / (G * (m2 / pow(R, 2));
}
NewtonsLawUniversalGravitationobject_mass = getFirstMass(force, distance, self_mass);
/**
* Return mass of first object from force, distance and mass
* of second object using Newton's law of universal gravitation
*
* @param double F Graviton force between objects
* @param double R Distance between objects
* @param double m2 Mass of second object
*
* @return double Mass of first object
*/
function getFirstMass(F, R, m2)
{
return F / (G * (m2 / pow(R, 2));
}
она должна именоваться таким образом, что бы не вызывать желания заглянуть в её код.
object_mass = getFirstMass(force, distance, self_mass); по-моему вполне информативна сама по себе чтобы понимать, что функция возвращает и на основании чего. Дублировать информацию как в случае getMassFrom<b>ForcePlanetMass</b>And<b>Radius</b>(force, planetMass, radius); не считаю необходимым.function getFirstMass(F, R, m2)
{
return F / (G * (m2 / pow(R, 2));
}
Код тоже ненадежный источник информации.
от, кто не синхронизирует комментарии с кодом из-за лени, ещё вероятней не будет синхронизировать именование функций/методов и их внутреннюю логику, поскольку это требует дополнительных действий.
Код — единственный надёжный источник информации! Только в коде мы видим, как есть.
А как «должно быть» — этого до конца не знает никто, включая заказчика.
Ровно наоборот, синхронизовать названия переменных, функций и классов гораздо проще, ибо все современные IDE умеют делать это автоматически. При любом рефакторинге автоматически меняют все нужные названия.
function getMassFromForcePlanetMassAndRadius(force, planetMass, radius) {
return force / (G * (planetMass / pow(Radius, 2))
}
function getMassFromForcePlanetMassAndRadius(force, planetMass, radius)
function getBodyMass(gravityForce, planetMass, distanceFromBodyToPlanetCentre)
Я считаю, что код не должен читать человек, который не знает языка программирования и который не знает предметной области. Код — не учебник по этим вещам. Код создает нечто новое на стыке этих известных до этого кода знаний. И если человек не обладает знаниями языка программирования (ему разжевывают в комментариях), или не имеет минимальных представлений о предметной области — то и читать код ему не зачем, чем меньше он знает о коде и где он лежит, тем лучше для кода.
[theCar openDoor:kLeftDoor withKey:momsKey howFast:kVerySlow];, где openDoor:withKey:howFast: — селектор, он же имя метода. Такой код в разы читабельней класической нотации funcName(arg1,arg2,arg3), тк не нужно дергать IDE чтобы узнать, зачем нужен каждый аргумент, при плохом наименовании переменных./// <summary>
/// Adds Person
/// </summary>
/// <param name="age">Age of Person</param>
/// <param name="phoneNumber">Phone Number of Person</param>
void AddPerson(int age, string phoneNumber)это создает нехорошие соблазны для команды
b[0] = '\r';
b[1] = '\n';
b += 2;
*b++='\r';
*b++='\n';
«Не надо пристегиваться и устанавливать подушки безопасности»
Ведь если ездить внимательно, и соблюдать ПДД — никаких аварий не будет!
Во-первых, вся статья о том, что нужно более чётко следовать доктринам SOLID, т.к. в большинстве случаев (и в статье как раз об этом примеры) код с коментариями противоречит минимум SRP принципу.
Книга как раз говорит не только о том как нужно рефакторить старый код, но и зачем это делать.
Вот только не надо пытаться свои предпочтения выдать за факты.
статья о том, как нужно писать код
Don't worry, we aren't saying that people shouldn't write comments. In our olfactory analogy, comments aren't a bad smell; indeed they are a sweet smell. The reason we mention comments here is that comments often are used as a deodorant. It's surprising how often you look at thickly commented code and notice that the comments are there because the code is bad.
[...]
A good time to use a comment is when you don't know what to do. In addition to describing what is going on, comments can indicate areas in which you aren't sure. A comment is a good place to say why you did something. This kind of information helps future modifiers, especially forgetful ones.
Это не то, о чем статья, это то, что вы в ней вычитали. Это совсем не одно и то же.
Ага. Но комментарии и SOLID тут не при чем (более того, Фаулер вообще ни разу про SOLID в этой книге не упоминает). Собственно, здесь вы и подменяете понятия.
Т.е. то, что я могу неправильно понять статью это допустимо, но вы не допускаете мысли о том, что вы сами неправильно всё поняли?
Как я уже упомянул, код напичканый комментариями в подавляющем большинстве случаев представляет собой явный пример нарушения SRP принципа
И появление комментариев в коде тоже должно вас, как хорошего разработчика, насторожить — возможно, это вполне оправданно, но как правило это явный признак лени сделать сразу как надо (разнести по методам или применить другие методы рефакторинга).
К сожалению, я не читал предлагаемой вами книги
www.linorg.ru/how-to-read.html
Дался вам этот SRP… нет, не является. Комментарии в коде могут означать что угодно, от желания к излишней подробности до неудачного дизайна. Самый частый случай комментариев на моем опыте — это неудачное именование; следующий по частоте — недостаточная выразительность языка в части контрактов (попробуйте в .net без комментариев указать, какие исключения и когда бросает метод), потом — желание писать тривиальные вещи «лишь бы были комментарии», и только потом — группировка кода. И да, здесь осознанно перемешаны нужные и ненужные комментарии.
(я, естественно, не рассматриваю документирующие комментарии)
Вы знаете, что является необходимым условием для рефакторинга?
Понятно. В этом, простите за прямоту, ваша беда. Особенно, конечно, это смешно, учитывая следующую цитату из столь любимого вами Фаулера (причем прямо из той книги, которую вы приводите): «McConnell, Code Complete [...] An excellent guide to programming style and software construction. Written before Java, but almost all of its advice applies.» Почитайте, это полезно. Внезапно узнаете, что то, что я цитирую из Фаулера — это всего лишь сокращенная (и упрощенная) версия того, что пишет МакКоннелл.
Собственно, в этом и состоит моя к вам претензия: не надо подменять понятия (комментарии vs SOLID) и книги.
Причём тут это?
Вы не согласны с тем, что написание коментариев в блоках кода должно вас насторожить и вы должны подумать «а не нужно ли здесь переделать код»?
Скажите, а где я сказал, что вы цитируете Фаулера?
комментарии в большинстве случаев (а особенно в тех, о чём пишется в данной статье) являются следствием нарушения SOLID принципов, а именно SRP принципа
main как делала несколько вещей, так продолжает их делать, количество ответственностей кода не изменилось.You've probably heard that a class should be a crisp abstraction, handle a few clear responsibilities, or some similar guideline. In practice, classes grow. You add some operations here, a bit of data there. You add a responsibility to a class feeling that it's not worth a separate class, but as that responsibility grows and breeds, the class becomes too complicated. Soon your class is as crisp as a microwaved duck.
Such a class is one with many methods and quite a lot of data. A class that is too big to understand easily. You need to consider where it can be split, and you split it. A good sign is that a subset of the data and a subset of the methods seem to go together. Other good signs are subsets of data that usually change together or are particularly dependent on each other.
Вы не согласны с тем, что написание коментариев в блоках кода должно вас насторожить и вы должны подумать «а не нужно ли здесь переделать код»?
и в статье как раз об этом примеры
SortWithMiniumMemoryUsingBecauseOurUsersHaveNotMoneyToBuy64GbRamServer()? Вам не кажется, что такое имя функции маразмом попахивает?Грубо говоря, только те, которые не являются параметрами алгоритмов. Если алгоритму можно указать сколько памяти он может и/или должен брать (например под кэш), то это в худшем случае будет константа, ьа скорее попадет в конфиг и/или параметр командной строки.
А кроме как минимум одного класса (хотя вы говорили о двух) нужно будет ещё реализовывать управление выбором нужного класса. Да и с одним классом заказчик может косо посмотреть на исполнителя, когда прочитает в документации что-то вроде «значение sort_method в конфигурационном файле означает используемый метод сортировки, допустимое значение только „minimum_memory“. Особенно это будет интересно, если это единственное значение в конфиге и вся подсистема работы с ним (включая логику фабрик) введена (в особых случаях написана с нуля) только ради него.
Ну, если это константа, то она не должна зависеть от внешних факторов. А в вашем случае это диктуется железом сервера. На мой взгляд это отличный пример конфигурационной зависимости.
Но в реальных ООП системах у нас уже как правило подключён DI и управление подобной зависимостью тривиальный шаг.
Почему не должна? Изменились требования изменилась и константа. В случае компилируемых языков ещё есть смысл выносить её в конфиг, чтобы не перекомпилировать приложение, но вот в случае интерпретируемых языков зачастую проще собрать все константы в один файл или каталог, чем заморачиваться с парсингом конфига.
DI имеет разные реализации и вовсе не всех из них требуют внешних конфигов. Главное что клиенты объектов получают их извне, а не создают сами, а созданы они путем обработки конфигов или просто захардкожены на уровне выше в стэке вызовов — особо им без разницы.
И вы перекомпилируете код и выкладываете на продакшн новую версию? Вы простите, но это какие-то уж совсем экзотические примеры вы придумываете. Я пишу веб сайты, и мне ваши примеры кажутся высосаными из пальца. Если у меня что-то зависит от железа, то это пишется в конфиге или в БД, а не хардкодится. Уж не знаю что вы там пишете, что может оправдать (как по мне) подобную дикость.
для разделения на блоки длинных функций, чтобы было проще ориентироваться
То, что хочется написать как комментарий к блоку — напишите как название функции.
Вы же пеняли на то, что открыв метод, тяжело понять что он делает.
А растут они из внешних (по отношению к приложению) условий
/// <devdoc>
/// Ok, this class needs some explanation. We share message loops with other applications through
/// an interface called IMsoComponentManager. A "component' is fairly coarse here: Windows Forms
/// is a single component. The component manager is the application that owns and runs the message
/// loop. And, consequently, an IMsoComponent is a component that plugs into that message loop
/// to listen in on Windows messages. So far, so good.
///
/// Because message loops are per-thread, IMsoComponentManager is also per-thread, which means
/// we will register a new IMsoComponent for each thread that is running a message loop.
///
/// In a purely managed application, we satisfy both halves of the equation: Windows Forms
/// implements both the IMsoComponentManager and the IMsoComponent. Things start
/// to get complicated when the IMsoComponentManager comes from the COM world.
///
/// There's a wrinkle with this design, however: It is illegal to call IMsoComponentManager on a
/// different thread than it expects. In fact, it will throw an exception. That's a probolem for key
/// events that we receive during shutdown, like app domain unload and process exit. These
/// events occur on a thread pool thread, and because as soon as we return from that thread the
/// domain will typically be torn down, we don't have much of a chance to marshal the call to
/// the right thread.
///
/// That's where this set of classes comes in. We actually maintain a single process-wide
/// application domain, and within this app domain is where we keep all of our precious
/// IMsoComponent objects. These objects can marshal to other domains and that is how
/// all other user-created Windows Forms app domains talke to the component manager.
/// When one of these user-created domains is shut down, it notifies a proxied
/// IMsoComponent, which simply decrements a ref count. When the ref count reaches zero,
/// the component waits until a new message comes into it from the component manager.
/// At that point it knows that it is on the right thread, and it unregisters itself from the
/// component manager.
///
/// If all this sounds expensive and complex, you should get a gold star. It is. But, we take
/// some care to only do it if we absolutely have to. For example, If we only need the additional
/// app domain if there is no executing assembly (so there is no managed entry point) and if
/// the component manager we get is a native COM object.
///
/// So, if you're with me so far you probably want to know how it all works, probably due to some
/// nasty bug I introduced. Sorry about that.
///
/// There are two main classes here: ComponentManagerBroker and ComponentManagerProxy.
///
/// ComponentManagerBroker:
/// This class has a static API that can be used to retrieve a component manager proxy.
/// The API uses managed remoting to attempt to communicate with our secondary domain.
/// It will create the domain if it doesn't exist. It communicates with an instance of itself
/// on the other side of the domain. That instance maintains a ComponentManagerProxy
/// object for each thread that comes in with a request.
///
/// ComponentManagerProxy:
/// This class implements both IMsoComponentManager and IMsoComponent. It implements
/// IMsoComponent so it can register with with the real IMsoComponentManager that was
/// passed into this method. After registering itself it will return an instance of itself
/// as IMsoComponentManager. After that the component manager broker stays
/// out of the picture. Here's a diagram to help:
///
/// UCM <-> CProxy / CMProxy <-> AC
///
/// UCM: Unmanaged component manager
/// CProxy: IMsoComponent half of ComponentManagerProxy
/// CMProxy: IMsoComponentManager half of ComponentManagerProxy
/// AC: Application's IMsoComponent implementation
/// </devdoc>
Не пишите комментарии к коду!