Одиноким пятничным вечером в поисках вдохновения вы решаете припомнить свои былые победы на программистском фронте. Архив со старого жесткого диска неторопливо открывается и вот перед вами разворачивается код славных далеких времен…

О нет. Это совсем не то, что вы ожидали увидеть. Правда, что ли, все было настолько плохо? Почему вам никто не сказал? Как можно было до такого докатиться? Такое количество операторов go-to в одной-единственной функции – это вообще законно? Вы поспешно закрываете проект. На секунду вас одолевает искушение удалить файл, и всё содержимое жёсткого диска заодно.



Ниже вы найдете коллекцию уроков, поучительных примеров и предостережений, которые я вынес из собственного путешествия в прошлое. Все имена приводятся без изменений, чтобы обличить виновных.

2004


Мне было тринадцать лет. Проект под называнием «Red Moon» представлял собой крайне амбициозную игру про воздушные битвы от третьего лица. Те немногочисленные фрагменты кода, которые я не скопировал символ в символ из пособия «Developing Games in Java», были позор позором. Давайте убедимся на конкретных примерах.

Я хотел, чтобы у игроков было несколько вариантов оружия и возможность выбирать. В планах это все выглядело так: модель оружия перемещается вниз и внутрь модели игрока, заменяются на следующую и так далее. Вот код анимации. Лучше сильно не вникайте.

public void updateAnimation(long eTime) {
	if(group.getGroup("gun") == null) {
		group.addGroup((PolygonGroup)gun.clone());
	}
	changeTime -= eTime;
	if(changing && changeTime <= 0) {
		group.removeGroup("gun");
		group.addGroup((PolygonGroup)gun.clone());
		weaponGroup = group.getGroup("gun");
		weaponGroup.xform.velocityAngleX.set(.003f, 250);
		changing = false;
	}
}

Хотелось бы обратить ваше внимание на пару занимательных фактов. Во-первых, оцените, сколько здесь переменных:

  • changeTime
  • changing
  • weaponGroup
  • weaponGroup.xform.velocityAngleX

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

И еще одно: в итоге я так и не собрался сделать больше одной модели оружия. То есть для каждого из этих типов использовалась одна и та же модель. От всего этого кода не было абсолютно никакого прока.

Как это исправить


Убрать лишние переменные. В данном случае состояние оружия можно было описать при помощи всего двух: weaponSwitchTimer и weaponCurrent. Всю остальную информацию можно было бы извлечь из них.

Явно инициализируйте все, что только можно. Эта функция проверяет, является ли оружие null и при необходимости запускает процесс инициализации. Поразмыслив хоть секунд тридцать, я мог бы сообразить, что в этой игре у пользователя всегда есть какое-то оружие, а если нет, то играть попросту невозможно и можно со спокойной совестью крашить всю программу.

Очевидно, в какой-то момент я столкнулся с NullPointerException в этой функции, но вместо того, чтобы задуматься, откуда оно там взялось, по-быстрому отделался проверкой на значение null, и на этом дело кончилось.

Проявляйте инициативу и принимайте решения самостоятельно! Не предоставляйте компьютеру разбираться самому.

Названия


boolean noenemies = true; // why oh why

Называйте переменные типа Boolean без использования отрицания. Если вы пишете в коде что-то подобное, значит пришла пора переосмыслить свой подход к жизни:

if (!noenemies) {
	// are there enemies or not??
}

Обработка ошибок


Подобные вещи попадаются в коде сплошь и рядом:

static {
	try {
		gun = Resources.parseModel("images/gun.txt");
	} catch (FileNotFoundException e) {} // *shrug*
	catch (IOException e) {}
}

Вы, наверное, сейчас думаете: «Надо как-то поизящней обработать эту ошибку! Ну, хотя бы сообщение пользователю вывести или что-то в этом духе». Но я, честно говоря, придерживаюсь противоположной точки зрения.

Устранять ошибки – это действительно никогда не лишнее, но вот с обработкой легко можно перестараться. В данном случае без модели оружия играть все равно невозможно, так что можно с тем же успехом крашить игру. Не пытайтесь с достоинством выйти из положения, если оно заведомо безвыходное.

К тому же, возвращаясь к сказанному выше, здесь вам нужно самостоятельно решить, какие ошибки считать исправимыми, а какие – нет. К несчастью, в Sun считают, что практически все ошибки на Java просто обязаны быть исправимыми, и в результате мы имеем случаи ленивой обработки – один из них приведен выше.

2005-2006


К этому времени я уже освоил C++ и DirectX. Я задумал создать переиспользуемый движок, чтобы человечество могло с пользой для себя припасть к кладезю мудрости и опыта, которые я скопил за свою долгую четырнадцатилетнюю жизнь.

Думаете, на первый трэйлер было больно смотреть? Вы ещё ничего не видели.

На тот момент я уже знал что объектно ориентированное программирование – это круто. Это знание привело к ужасам такого рода:

class Mesh {
public:
	static std::list<Mesh*> meshes; // Static list of meshes; used for caching and rendering
	Mesh(LPCSTR file); // Loads the x file specified
	Mesh();
	Mesh(const Mesh& vMesh);
	~Mesh();
	void LoadMesh(LPCSTR xfile); // Loads the x file specified
	void DrawSubset(DWORD index); // Draws the specified subset of the mesh
	DWORD GetNumFaces(); // Returns the number of faces (triangles) in the mesh
	DWORD GetNumVertices(); // Returns the number of vertices (points) in the mesh
	DWORD GetFVF(); // Returns the Flexible Vertex Format of the mesh
	int GetNumSubsets(); // Returns the number of subsets (materials) in the mesh
	Transform transform; // World transform
	std::vector<Material>* GetMaterials(); // Gets the list of materials in this mesh
	std::vector<Cell*>* GetCells(); // Gets the list of cells this mesh is inside
	D3DXVECTOR3 GetCenter(); // Gets the center of the mesh
	float GetRadius(); // Gets the distance from the center to the outermost vertex of the mesh
	bool IsAlpha(); // Returns true if this mesh has alpha information
	bool IsTranslucent(); // Returns true if this mesh needs access to the back buffer
	void AddCell(Cell* cell); // Adds a cell to the list of cells this mesh is inside
	void ClearCells(); // Clears the list of cells this mesh is inside
protected:
	ID3DXMesh* d3dmesh; // Actual mesh data
	LPCSTR filename; // Mesh file name; used for caching
	DWORD numSubsets; // Number of subsets (materials) in the mesh
	std::vector<Material> materials; // List of materials; loaded from X file
	std::vector<Cell*> cells; // List of cells this mesh is inside
	D3DXVECTOR3 center; // The center of the mesh
	float radius; // The distance from the center to the outermost vertex of the mesh
	bool alpha; // True if this mesh has alpha information
	bool translucent; // True if this mesh needs access to the back buffer
	void SetTo(Mesh* mesh);
}

Вдобавок я узнал, что комментарии – это круто, и это сподвигло меня оставлять изречения типа:

D3DXVECTOR3 GetCenter(); // Gets the center of the mesh

Впрочем, с этим классом есть проблемы и посерьезнее. Концепт Mesh – это какая-то туманная абстракция, которой не подберешь эквивалента из реальной действительности. Я даже когда её писал, ничего не понимал. Что она такое – контейнер, который содержит в себе вершины, индексы и прочие данные? Менеджер ресурсов, который загружает и выгружает данные на диск? Bнструмент для рендеринга, которые отправляет данные в GPU? Всё и сразу.

Как это исправить


Класс Mesh должен представлять собой простую структуру данных. В нем не должно быть умных типов. А это значит, что можно со спокойной душой выкинуть все геттеры и сеттеры и сделать все поля публичными.

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

Самый верный способ исправить комментарии – как правило, удалить их. Комментарии быстро становятся неактуальным белым шумом, который только сбивает с толку – компилятор на них все равно не смотрит. Я настаиваю, что от комментариев нужно избавляться, если только они не принадлежат к одной из следующих групп:

  • Комментарии, которые отвечают на вопрос «почему?», а не «что?». От них больше всего толку.
  • Комментарии, которые в нескольких словах описывают, что делает огромный кусок кода, который последует далее. Они облегчают процесс чтения и навигации;
  • Комментарии в декларации структуры данных, поясняющие, что содержит каждое поле. Зачастую они излишни, но в некоторых случаях интуитивно не понятно, как структура расположена в памяти, и тогда комментарии с подобным описанием необходимы.

2007-2008


Этот период своей жизни я называю «тёмные века PHP».


2009-2010


Я уже учусь в колледже. Работаю над шутером-мультиплеером на Python от третьего лица под названием «Acquire, Attack, Asplode, Pwn». Ничего не могу сказать в свое оправдание. Все ещё более позорно, только теперь еще с пикантным послевкусием нарушения авторских прав.

Когда я писал эту игру, меня только-только просветили, что глобальные переменные – это зло. Они превращают код в мешанину. Они позволяют функции А, меняя глобальное состояние, ломать/нарушать функцию Б, которая не имеет к ней никакого отношения. Они не работают с потоками.

Однако практически всему коду геймплея необходим доступ к состоянию мировой матрицы целиком. Я «разрешил» эту проблему тем, что сохранил всё в этом объекте и передал его в каждую из функций. И никаких вам глобальных переменных! Мне казалось, что я здорово придумал, ведь получается, что в теории можно запускать несколько автономных мировых матриц одновременно.

На деле world, по факту, являлся контейнером глобального состояния. Концепция с несколькими world была, разумеется, совершенно бессмысленна, никто её никогда не тестировал, и я сильно подозреваю, что работать она могла бы только при условии серьезного рефакторинга.
Те, кто вступает в секту противников глобальных переменных, открывают для себя целый мир креативных методов самообмана. Самый худший из них – это singleton.

class Thing
{
	static Thing i = null;
	public static Thing Instance()
	{
		if (i == null)
			i = new Thing();
		return i;
	}
}

Thing thing = Thing.Instance();

Крибле крабле бумс! Ни единой глобальной переменной в поле зрения! Но тут есть одно «но»: singleton куда хуже по следующим причинам:

  • Все потенциальные недостатки глобальных переменных сохраняются и здесь. Если вы думаете, что singleton не глобален, то хватит уже лгать себе;
  • В лучшем случае singleton добавляет в вашу программу ресурсозатратный оператор ветвления. В худшем это полноценный вызов функции;
  • Пока не запустишь программу, неизвестно, когда инициализируется singleton. Это еще один пример того, как ленивые программисты предоставляют системе решать то, что следовало спланировать еще на стадии проектирования.

Как это исправить


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

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

Параметры типа Boolean


class ObjectEntity:
	def delete(self, killed, local):
		# ...
		if killed:
			# ...
		if local:
			# ...

Возможно, вам приходилось писать код вроде такого:

class ObjectEntity:
	def delete(self, killed, local):
		# ...
		if killed:
			# ...
		if local:
			# ...

Здесь мы имеем четыре отдельных операций по удалению, которые очень похожи между собой — все незначительные различия завязаны на двух параметрах типа Boolean. Вроде бы всё логично. А теперь давайте посмотрим на клиентский код, который вызывает эту функцию:

obj.delete(True, False)

Выглядит не особо читабельно, да?

Как это исправить


Тут нужно смотреть на конкретный случай. Но есть один совет от Casey Muratori, который актуален всегда: начинайте с клиентского кода. Я убеждён, что никакой человек в своём уме такого клиентского кода, какой приводился выше, не напишет. Скорее уж напишет следующее:

obj.killLocal()

А уже потом пропишет имплементацию функции killLocal().

Названия


Может показаться странным, что я так напираю на названия, но, как говорится в старой шутке, это одна из двух неразрешенных проблем в программировании (вторая — инвалидация кэша и ошибки на единицу).

Внимательно посмотрите на эти функции:

class TeamEntityController(Controller):

	def buildSpawnPacket(self):
		# ...
	
	def readSpawnPacket(self):
		# ...
	
	def serverUpdate(self):
		# ...

	def clientUpdate(self):
		# ...

Ясно, что первые две функции связаны между собой, как и последние две. Но их названия никак не указывают на этот факт. Если я начну печатать self в IDE, эти функции не отобразятся одна за другой в меню автозаполнения.

Соответственно, лучше начинать название с общего, а заканчивать частным. Вот так:

class TeamEntityController(Controller):

	def packetSpawnBuild(self):
		# ...
	
	def packetSpawnRead(self):
		# ...
	
	def updateServer(self):
		# ...

	def updateClient(self):
		# ...

С таким кодом меню автозаполнения будет выглядеть намного логичнее.

2010-2015


Каких-то 12 лет работы — и я закончил игру. Пусть я многому научился в процессе, в конечной версии все-таки сохранились серьезные проколы.

Связывание данных


В то время только-только начиналась лихорадка реактивных UI фреймворков типа MVVM от Microsoft и Angular от Google. Сегодня подобный стиль программирования сохраняется преимущественно в React.

Все эти фреймворки работают по одной схеме. Вы видите текстовое поле для HTML, пустой элемент и одну-единственную строчку кода, которая неразрывно их связывает. Введите текст в поле — и вуаля! волшебным образом обновится.

В контексте игры это будет выглядеть примерно так:

public class Player
{
	public Property<string> Name = new Property<string> { Value = "Ryu" };
}

public class TextElement : UIComponent
{
	public Property<string> Text = new Property<string> { Value = "" };
}

label.add(new Binding<string>(label.Text, player.Name));

Ух ты, интерфейс теперь автоматически обновляется при введении имени игрока! Интерфейс и код игры могут быть совершенно независимы друг от друга. Это заманчиво: мы избавляемся от состояния интерфейса, выводя его из состояния игры.

Однако были некоторые тревожные звоночки. Мне пришлось превратить каждое поле в игре в Propert-объект, который включал целый ряд зависимых связей.

public class Property<Type> : IProperty
{
	protected Type _value;
	protected List<IPropertyBinding> bindings; 

	public Type Value
	{
		get { return this._value; }
		set
		{
			this._value = value;
			
			for (int i = this.bindings.Count - 1; i >= 0; i = Math.Min(this.bindings.Count - 1, i - 1))
				this.bindings[i].OnChanged(this);
		}
	}
}

Абсолютно за каждым полем, вплоть до последнего Boolean, был закреплен громоздкий динамический массив.

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

Тем не менее, я так проникся связыванием данных, что выстроил на нем всю игру. Я разбил объекты на компоненты и связал их свойства. Ситуация стала выходить из-под контроля.

jump.Add(new Binding<bool>(jump.Crouched, player.Character.Crouched));
jump.Add(new TwoWayBinding<bool>(player.Character.IsSupported, jump.IsSupported));
jump.Add(new TwoWayBinding<bool>(player.Character.HasTraction, jump.HasTraction));
jump.Add(new TwoWayBinding<Vector3>(player.Character.LinearVelocity, jump.LinearVelocity));
jump.Add(new TwoWayBinding<BEPUphysics.Entities.Entity>(jump.SupportEntity, player.Character.SupportEntity));
jump.Add(new TwoWayBinding<Vector3>(jump.SupportVelocity, player.Character.SupportVelocity));
jump.Add(new Binding<Vector2>(jump.AbsoluteMovementDirection, player.Character.MovementDirection));
jump.Add(new Binding<WallRun.State>(jump.WallRunState, wallRun.CurrentState));
jump.Add(new Binding<float>(jump.Rotation, rotation.Rotation));
jump.Add(new Binding<Vector3>(jump.Position, transform.Position));
jump.Add(new Binding<Vector3>(jump.FloorPosition, floor));
jump.Add(new Binding<float>(jump.MaxSpeed, player.Character.MaxSpeed));
jump.Add(new Binding<float>(jump.JumpSpeed, player.Character.JumpSpeed));
jump.Add(new Binding<float>(jump.Mass, player.Character.Mass));
jump.Add(new Binding<float>(jump.LastRollKickEnded, rollKickSlide.LastRollKickEnded));
jump.Add(new Binding<Voxel>(jump.WallRunMap, wallRun.WallRunVoxel));
jump.Add(new Binding<Direction>(jump.WallDirection, wallRun.WallDirection));
jump.Add(new CommandBinding<Voxel, Voxel.Coord, Direction>(jump.WalkedOn, footsteps.WalkedOn));
jump.Add(new CommandBinding(jump.DeactivateWallRun, (Action)wallRun.Deactivate));
jump.FallDamage = fallDamage;
jump.Predictor = predictor;
jump.Bind(model);
jump.Add(new TwoWayBinding<Voxel>(wallRun.LastWallRunMap, jump.LastWallRunMap));
jump.Add(new TwoWayBinding<Direction>(wallRun.LastWallDirection, jump.LastWallDirection));
jump.Add(new TwoWayBinding<bool>(rollKickSlide.CanKick, jump.CanKick));
jump.Add(new TwoWayBinding<float>(player.Character.LastSupportedSpeed, jump.LastSupportedSpeed));

wallRun.Add(new Binding<bool>(wallRun.IsSwimming, player.Character.IsSwimming));
wallRun.Add(new TwoWayBinding<Vector3>(player.Character.LinearVelocity, wallRun.LinearVelocity));
wallRun.Add(new TwoWayBinding<Vector3>(transform.Position, wallRun.Position));
wallRun.Add(new TwoWayBinding<bool>(player.Character.IsSupported, wallRun.IsSupported));
wallRun.Add(new CommandBinding(wallRun.LockRotation, (Action)rotation.Lock));
wallRun.Add(new CommandBinding<float>(wallRun.UpdateLockedRotation, rotation.UpdateLockedRotation));
vault.Add(new CommandBinding(wallRun.Vault, delegate() { vault.Go(true); }));
wallRun.Predictor = predictor;
wallRun.Add(new Binding<float>(wallRun.Height, player.Character.Height));
wallRun.Add(new Binding<float>(wallRun.JumpSpeed, player.Character.JumpSpeed));
wallRun.Add(new Binding<float>(wallRun.MaxSpeed, player.Character.MaxSpeed));
wallRun.Add(new TwoWayBinding<float>(rotation.Rotation, wallRun.Rotation));
wallRun.Add(new TwoWayBinding<bool>(player.Character.AllowUncrouch, wallRun.AllowUncrouch));
wallRun.Add(new TwoWayBinding<bool>(player.Character.HasTraction, wallRun.HasTraction));
wallRun.Add(new Binding<float>(wallRun.LastWallJump, jump.LastWallJump));
wallRun.Add(new Binding<float>(player.Character.LastSupportedSpeed, wallRun.LastSupportedSpeed));
player.Add(new Binding<WallRun.State>(player.Character.WallRunState, wallRun.CurrentState));

input.Bind(rollKickSlide.RollKickButton, settings.RollKick);
rollKickSlide.Add(new Binding<bool>(rollKickSlide.EnableCrouch, player.EnableCrouch));
rollKickSlide.Add(new Binding<float>(rollKickSlide.Rotation, rotation.Rotation));
rollKickSlide.Add(new Binding<bool>(rollKickSlide.IsSwimming, player.Character.IsSwimming));
rollKickSlide.Add(new Binding<bool>(rollKickSlide.IsSupported, player.Character.IsSupported));
rollKickSlide.Add(new Binding<Vector3>(rollKickSlide.FloorPosition, floor));
rollKickSlide.Add(new Binding<float>(rollKickSlide.Height, player.Character.Height));
rollKickSlide.Add(new Binding<float>(rollKickSlide.MaxSpeed, player.Character.MaxSpeed));
rollKickSlide.Add(new Binding<float>(rollKickSlide.JumpSpeed, player.Character.JumpSpeed));
rollKickSlide.Add(new Binding<Vector3>(rollKickSlide.SupportVelocity, player.Character.SupportVelocity));
rollKickSlide.Add(new TwoWayBinding<bool>(wallRun.EnableEnhancedWallRun, rollKickSlide.EnableEnhancedRollSlide));
rollKickSlide.Add(new TwoWayBinding<bool>(player.Character.AllowUncrouch, rollKickSlide.AllowUncrouch));
rollKickSlide.Add(new TwoWayBinding<bool>(player.Character.Crouched, rollKickSlide.Crouched));
rollKickSlide.Add(new TwoWayBinding<bool>(player.Character.EnableWalking, rollKickSlide.EnableWalking));
rollKickSlide.Add(new TwoWayBinding<Vector3>(player.Character.LinearVelocity, rollKickSlide.LinearVelocity));
rollKickSlide.Add(new TwoWayBinding<Vector3>(transform.Position, rollKickSlide.Position));
rollKickSlide.Predictor = predictor;
rollKickSlide.Bind(model);
rollKickSlide.VoxelTools = voxelTools;
rollKickSlide.Add(new CommandBinding(rollKickSlide.DeactivateWallRun, (Action)wallRun.Deactivate));
rollKickSlide.Add(new CommandBinding(rollKickSlide.Footstep, footsteps.Footstep));

Это вызвало массу проблем. Я создавал циклы связей, которые закольцовывались. Выяснилось, что порядок инициализации часто имеет большое значение, а при связывании данных инициализация становится настоящим адом, так как по мере добавления связей некоторые свойства инициализируются по несколько раз.

Когда пришло время добавлять анимацию, оказалось, что со связыванием данных анимировать переход между двумя состояниями — сложный и неинтуитивный процесс. И не я один так думаю. Можете посмотреть это видео с программистом Netflix, который рассыпается в похвалах React, а потом рассказывает, как его приходится отключать всякий раз, когда они запускают анимацию.

Я тоже догадался применить всю мощь отключения связей. И добавил еще одно поле:

class Binding<T>
{
	public bool Enabled;
}

К сожалению, от этого терялся весь смысл связывания. Я же хотел избавиться от состояний, а с таким кодом их только больше стало. Вот как убрать это состояние?

Знаю! При помощи связывания!

class Binding<T>
{
	public Property<bool> Enabled = new Property<bool> { Value = true };
}

Да, был момент, когда я серьёзно пытался что-то подобное реализовать. Связывал всё, что связывается. Но я быстро опомнился и понял, что это безумие.

Как можно исправить ситуацию со связыванием? Постарайтесь сделать так, чтобы ваш интерфейс нормально функционировал без состояний. Хороший пример — dear imgui. Разделяйте поведение и состояние, насколько это возможно. Избегайте техник, с которыми легко создавать состояния. Создание состояние должно быть для вас мучительным шагом.

Заключение


Можно было бы продолжать, это ещё далеко не все мои глупейшие ошибки. Я сочинил еще один «оригинальный» метод избегать глобальных переменных. Был период, когда я помешался на замыканиях. Я создал так называемую систему сущность-объект, и это было что угодно, но не система сущность-объект. Я попытался реализовать многопоточность в воксельном движке, щедро наставив блокировок.

Вот выводы, к которым я пришел:

  • Принимайте решения самостоятельно, не делегируйте их компьютеру;
  • Разделяйте поведение и состояние;
  • Пишите чистые функции;
  • Начинайте с клиентского кода;
  • Пишите скучный код.

Вот вам моя история. А у вас есть тёмное прошлое, которым вы готовы поделиться?
Поделиться с друзьями
-->

Комментарии (39)


  1. Tatooine
    05.04.2017 14:14
    -26

    Что касаемо переменных то лично я для себя завел правило какое-то время назад — все переменные одного типа отправлять в массив.
    И работать с массивами переменных.


    1. KumoKairo
      05.04.2017 15:46
      +2

      Не очень понятно что имеется в виду. Переменные, например, одного типа int? Независимо от назначения этих переменных?


    1. zagayevskiy
      05.04.2017 15:47
      +19

      Позвольте спросить, сколько вам лет, и на чём вы пишите?


      1. m1n7
        05.04.2017 16:34
        -5

        "не меньше чем тебе" и "на английском"


        1. zagayevskiy
          05.04.2017 16:51
          +5

          Логичнее было бы "на бумаге", но ок.


        1. Mendel
          05.04.2017 16:53

          эм… клон?


          1. anmipo
            06.04.2017 11:38
            +2

            Сарказм. Кавычки же.


          1. rageOfAxe
            07.04.2017 15:36

            ирония


      1. player
        07.04.2017 15:36

        Я ни на что не намекаю, но: «Состоит в: PHP»


    1. NightmareZ
      06.04.2017 04:17
      +2

      Даю подсказку. Во многих (а то и во всех) языках есть возможность положить в массив данные любого типа. Просто массив объявляется как массив объектов типа (в зависимости от ЯП) Variant, dynamic, object, void* — и ты можешь складывать в него все свои данные! Одна переменная на всю программу!


      1. MacIn
        06.04.2017 16:24

        А зачем нам массив, если вообще можно складывать все по одному указателю? Работать не будет, но зато как красиво!


    1. Pakos
      06.04.2017 09:27

      a[i], конечно, понятнее, чем someVar. Но если писать на английском, то это решает проблему.


    1. ellrion
      06.04.2017 14:08
      +3

      "Твое последнее задание в игре синий хабр: самым первым оставить глупый комментарий под статьей "


    1. ShinRa
      07.04.2017 15:37

      Вы серьезно? Это же ужасно. Если у Вас бывает так много переменных в классе (читай полей), задумайтесь, что-то пошло не так.
      Ладно, если действительно необходимо создать много объектов, и поместить их даже не в массив, а в список, т.е. Вы заранее не будете знать размер массива.
      И вообще, либо я Вас не так понял, либо действительно все так плохо


  1. andreysmind
    05.04.2017 14:14
    +12

    Нет, у меня нет темного прошлого. Как первый раз увидел компьютер «Байт», так — вжух — и стал писать идеальный код в соответствии с Шаблонами Программирования и Лучшими Практиками.

    Не очень понял смысл этого каминг-аута.


    1. Anarions
      05.04.2017 15:10
      +1

      Разбор типичных ошибок, попытки объяснить как делать лучше вместо таких ошибок.


      1. andreysmind
        05.04.2017 15:23
        +2

        Комменты — зло.
        Синглтон — зло.
        Объекты — зло.
        Мутабельность зло.
        Только чистые функции, только хардкор.

        Это не попытка объяснить как лучше, а то что на английском называется «jump on the bandwagon».
        Чувак в очередной раз ПРОЗРЕЛ и выдает это за откровение.


        1. Anarions
          05.04.2017 15:40
          +1

          Поэтому написал «попытки».


    1. Crandel
      06.04.2017 23:41
      +1

      Всем привет, меня зовут %username% и я пишу кошмарный код :)


    1. Kryshtop
      07.04.2017 15:36

      good


  1. GarryC
    05.04.2017 15:29
    +3

    Плохой программист из какой угодно парадигмы сделает ужас, хороший в любой парадигме напишет вполне приемлемый код.


  1. sbnur
    05.04.2017 15:47

    Один говорит — ошибки, а другой думает — опыт
    Ребенок коверкает слова, придавая им часто нетривиальный смысл.
    Взрослый говоит формально правильно в своей устоявшейся языковой среде.
    Старик впадает в детство


    C'est la vie


    1. AllexIn
      05.04.2017 18:25

      Я — старик… Постоянно придумываю новые слова из нескольких старых. Или из иностранных формирую странное русское… А ведь всего тридцать. :(((


  1. Mendel
    05.04.2017 15:57
    +4

    Самое ужасное что вспоминается это первые дни в php.
    Я тогда изобрел офигенный способ сделать динамические пути с единой точкой входа.
    мод_реврайт мне был незнаком, поэтому у меня точка входа была прописана как кастомная страница ошибки 404.
    Апач не находил запрошенную страницу и подставлял мою страницу, а она уже разбирала что и куда.
    Идея прожила наверное целую неделю))).
    Но это так, на поржать конечно. Пользы от таких воспоминаний никакой.

    Типичная ошибка которую совершают большинство программистов (как новичков так и считающих себя опытными) это неверное понимание сути парадигм.
    1- ООП. Тяжело понять что ООП это в первую очередь SOLID. Понять SOLID тяжело. Можно пять раз прочитать описание в вики. Можно десять. Все равно первое понимание ООП будет неким механизмом собрать родственные функции в одном файле, да еще и переменных ему подсунуть. Класс из одного метода длиной в полторы тысячи строк? Да легко. (Не у меня, со знакомства с понятиям класса я больше 200 строк на метод не писал, что тоже ад). 60 методов в одном классе? Легко! К ним еще 20 параметров и ура в бой (было). Один класс, одна ответственность? Ну так у меня одна ответственность. У автомобиля одна ответственность — ехать. Так что можно сразу функции колес и двигателя в один класс.
    2 — Толстые Тупые Уродливые Контроллеры. Это даже хуже чем goto. И тоже типичное понимание из определения/описания.
    Таких примеров масса. Мы не понимаем. И используем не паттерн а свое искаженное понимание.

    Да, а еще я люблю то что происходит с кодом без рефакторинга. Год без рефакторинга. Без остановки и осмотра всего целиком. Сейчас такие чудеса вычищаю что аж страшно. Никогда бы не написал если бы писал то что написано. Но… задача менялась, оно развивалось и вышло то что вышло.

    Самая страшная ошибка новичка — до меня с этим никто не сталкивался. Есть такая чудесная библиотека в PHP. Называется VQMOD. (На самом деле подобных библиотек несколько существует т.е. это не один такой «гений»).
    Автор писал «ООП и MVC» код в котором встречались контроллеры по несколько тысяч строк в одном методе.
    Естественно иногда их хотелось изменить. Но не глобально, а только в этом проекте. Чтобы не потерять изменение при обновлении. Что он придумал? Дробить метод на части и переопределять? DRY и SOLID? Нее. Он написал библиотеку которая создает копию нужного кода и вносит в этот код изменения описанные в специальном формате в XML. Понимаете да? Программа изменяет свой исходный код, потому что автор ниасилил наследование. Подумать что ты не первый кому надо что-то изменить в существующем коде не меняя кода физически — не судьба. Будем сразу писать.


    1. MacIn
      05.04.2017 22:45

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

      Ух ты, а мне понравилась эта идея! Не, не в плане использовать в работе, а степенью нестандартности.


      1. Mendel
        06.04.2017 00:55

        Свят, свят. Это адовый ад на практике.
        Но я знаю за что мне это (мучения с тем кодом где это используется).
        Вот за это.
        Оказывается пять лет назад я предлагал нечто подобное, только не до такой степени ужасности, но направление глупости тоже самое. Но самый страшный мой грех в том, что меня тут на хабре тыкали носом, что это ужасно, но я настаивал). Впрочем осознал я это быстро, и в продакшн оно не ушло. Хотя в малых дозах оно пригодно). Идею в ключевых местах делать пустой класс у которого есть только имя родителя и всё, чтобы при необходимости править его, а не ядро — использую до сих пор.


    1. Hidadmin
      06.04.2017 09:25

      мод_реврайт мне был незнаком, поэтому у меня точка входа была прописана как кастомная страница ошибки 404.
      Апач не находил запрошенную страницу и подставлял мою страницу, а она уже разбирала что и куда.
      Не поверите — у меня есть CMS (малоизвестная, если только в узких кругах) у которой ЧПУ основано на этом методе )


      1. Mendel
        06.04.2017 11:29

        Ну если цель — скрыть сайт от поисковиков, то норм решение).
        Хотя возможно можно и убить стандартный хеадер, не знаю как апач себя ведет в этом случае.
        Ну еще лог ошибок будет дублировать лог посещения.


    1. dastan1992
      07.04.2017 15:36

      Встречал VQMOD в opemcart проектах. Противная штука.


      1. Mendel
        07.04.2017 16:43

        Как и сам опенкарт.
        Неплохая в принципе CMS с огромным вагоном функционала.
        Но под капотом дерьм-дерьмом.
        Страшно представить что бы это был за самолет если бы его автор умел бы писать программы)


  1. AllexIn
    05.04.2017 18:26
    +3

    Хм. А я всегда думал что программисты живут осознанием, что вчерашний код был хуже сегодняшнего…
    Узнаешь новый вариант решения, начинаешь его применять, вполне осознаешь что старый был хуже. И так — постоянно.


    1. Alikberov
      07.04.2017 15:36
      -1

      Иногда пересматривал свой код спустя лет 10 и находил строчек 20, которые можно сократить до 5-10. И всегда мучал вопрос «Как я тогда не додумался?»…


  1. fastwit
    06.04.2017 11:52
    +1

    Всегда удобно скатиться к слепому следованию простым правилам или методикам, а-то и просто чьим-то выводам. Видимо, эту природу человека сложно перебороть. Поиск пресловутой «серебрянной пули» преобладает над поиском инженерных или научных решений. Отсюда и появляется эта «геология» кода. Когда подобно тому, как кольца на срезе ствола дерева, запечатляют события и окружение, отпечаток времени, если хотите, так и в программном коде мы видим отражение эпох в развитии индустрии. До выхода книги «банды четырех» был код-как-код, у кого-то стройный и красивый, у кого-то просто рабочий, а после всюду появились синглтоны, фабрики, посетители. Но баланс между элегантным кодом и просто рабочим кодом практически не изменился. Новые фреймворки, новые веяния, все то новое, что мы запечатляем в нашем коде — это, лишь, мода.
    Легко сказать юному программисту, что «это и то — зло», что нужно делать «вот так и так». Но со временем, этих ограничений становится больше, одни сменяют другие. Если в своей работе мы думаем подобными рецептами, то это находит отражение в коде. Откуда берутся «костыли»? Они необходимы для поддержки угловатых, шероховатых и грубых подходов.
    Код, который мы пишем в настоящем, с большой вероятностью, будет легаси-кодом в будущем. И причина тому, не столько скачок технологий вперед, сколько эта «мода», «хайп» и поиск «волшебного решения». Программист — тот, кто пишет программы на языке программирования. Язык — ключевое слово. Мы выражаем наши мысли на яызке программирования. Но, опять же, что если в мыслях только одни строгие рецепты и указания?
    Программирование — это творчество, но не в романтичном смысле, каким обычно оно представляется обкуренными хиппи, создающими инсталляцию из консервных банок. Творчество это, сродни тому, что есть у физиков, математиков, инженеров. Оно должно быть основано на фундаментальной подготовке. Научный подход, инженерный подход, сообщество (чтение статей и чужого кода, в данном случае). Здесь нет места оправданиям «я так чувствую», «я так вижу». Программы должны быть минимальными, достаточными и эффективными. Хотя бы стремиться быть таковыми. Что лежит в основе нашей профессии? Математика, алгоритмы и ограничения текущего железа.


    1. Mendel
      06.04.2017 12:28
      -1

      Любой дурак может написать программу, которую поймет компилятор. Хорошие программисты пишут программы, которые смогут понять другие программисты.
      Фаулер Мартин.

      Всё остальное шелуха.
      Стандарты. Будь то стандарты оформления кода, паттерны или другие стандарты — решают проблему читаемости кода и ускоряют разработку. Ускоряют двумя путями — когда код более понятный другим, он более понятен и тебе. Ну и дополнительно — готовые паттерны уже готовы, и можно меньше выдумывать.


      1. fastwit
        06.04.2017 16:07
        +2

        Любой комментатор может вставить афоризм от признанного авторитета, но не каждый способен понять и оценить границы смысла, границы авторитета и уместность.
        Я.


        Ну вот, я переживал, что написал комментарий-портянку с капитанскими утверждениями, а тут такое… Стандарты — это хорошо, кто же спорить будет, умолчим о ситуации, когда их слишком много. Но комбинация фраз «готовые паттерны уже готовы, и можно меньше выдумывать» — это как раз то, о чем я писал. Слепое следование. А, ведь, паттерны как раз-таки не готовы, они — полуфабрикаты из которых вам, обязательно с приложением ума и усилий, необходимо приготовить ваше решение.
        Понимание другими программистами — спорная метрика. Я вспоминаю, как пришел на первый свой большой проект, я уже был профессиональным программистом. До этого я в одиночку несколько лет писал свой сервис на PHP и пару shareware программ на C, заморачиваясь за минимальность и всякие оптимизации, иногда даже чересчур. И вот я вижу enterprise код. На Java. Я, будучи другим программистом, совсем не понял, что они там понаписали. Было ощущение, что они все усложняли и хотелось выкинуть половину ненужного кода. И только потом, со временем, я дорос.


        1. Mendel
          06.04.2017 16:44
          -1

          В "портянке" выше я достаточно подробно коснулся вопроса ПОНИМАНИЯ паттернов).


  1. XeL077
    07.04.2017 15:36
    +1

    Нашел как-то свой старый свой код, который когда-то потерял, очень горевал в свое время, что не смогу использовать свои библиотеки и компоненты.
    Лучше бы не находил.


  1. vesper-bot
    07.04.2017 15:36
    +1

    Когда мне было 9 лет, и я не знал, что такое алгоритм сортировки, я написал сортировку четырех элементов на Турбо-Паскале на обычных условных операторах в 150 строк в виде единого дерева. Даже тогда я понял, что так писать нельзя. И только на следующий день я узнал, что в Паскале есть такая штука — цикл.


  1. tmteam
    07.04.2017 15:36

    Признаюсь. Я писал IDE редактор, разделив его на два слоя — ядро (правильное решение) и всё остальное (ой).
    Под всем остальным понималась смесь модели, вьюмодели ксамл кс вьюх (дело было в впф), логики и чего то ещё( похоже мой мозг стирает такие негативные воспоминания). Неймспейсы и папки проекта были рандомными, а в одном файле могло быть до 10ти классов. И не обязательно однотипных.
    Вообще ничего не было обязательным. Сальвадору дали дали студию. Зря ;)

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

    Последние 7 месяцев были потрачены на разделение этого барахла на 4 слоя, с юнит тестами SOLID, и MVVM. Это лучший опыт в моей жизни, хоть и на мёртвой технологии. Теперь дзен…

    П.С. Я не думал что когда нибудь это всё расскажу, но раз такая пьянка… С души отлегло.