За последнюю неделю я потратил около 24 часов на создание приложения под названием «Груша». За эти дни кодовая база выросла почти до 4 тысяч строк. В моем последнем более крупном проекте, копии веб-сайта «Люди Нью-Йорка», я хотел сделать все идеально, когда я только выпустил его. Я, наверное, потратил 200 часов в течение месяца, чтобы попытаться усовершенствовать каждую функцию, но когда я перешел в рабочий режим, все эти новые проблемы возникли. Так что на этот раз я подумал: ну, черт возьми, я просто сначала сделаю доказательство концепции, а затем постараюсь улучшить это.
Поэтому я решил погрузиться в свой проект, сохранив краткий список задач на доске проектов github, а остальная часть плана витала в моей голове. Как вы думаете, где это меня? Это привело меня к воскресному утру, где я смотрю программу, о которой трудно рассуждать. Тот, который поражает меня каждым новым компонентом.
Я хочу потратить некоторое время, чтобы перебрать те ошибки, которые привели меня сюда.
- Я не видел основной структуры того, что на самом деле является просто "грязным" приложением.
Идея проекта - создать инструмент для совместной работы начинающих программистов. Я хотел, чтобы у пользователей была возможность (1) отправлять друг другу письма, (2) присоединяться и создавать проекты кода.
Это что-то среднее между Hello World и открытым исходным кодом.
(Я использую GraphQL, поэтому с этого момента я буду использовать язык запросов и мутаций.)
Итак, что мне нужно сделать, чтобы реализовать перечисленные выше функции? Мне нужно создавать, читать, обновлять и удалять ресурсы. Для моих запросов это означает, что у меня, вероятно, должен быть способ получить ресурс по идентификатору и способ получить тот же ресурс по массиву идентификаторов. Это можно легко превратить в один запрос, но я сделал два разных запроса. Я даже создал специальный маршрут для получения активных проектов, когда я мог сделать один запрос проекта с разными необязательными параметрами.
Вот.
import { GraphQLList, GraphQLID as IDType } from 'graphql' import ProjectModel from '../../../models/project' export default { type: new GraphQLList(IDType), async resolve (root, params, options) { let Projects = await ProjectModel.find({ 'details.status': 'Active' }).exec() return Projects.map((a) => a._id) } }
2. Я пытался предвидеть проблемы до того, как они возникнут.
Для более продвинутого программиста это не проблема. На самом деле, это даже звучит как умный совет рассказать кому-нибудь. Для меня это доставило только ненужные хлопоты.
Обычно ошибки начинаются, когда я начинаю моделировать свои данные
const project = { participants: { owner: { type: Schema.Types.ObjectId, ref: 'user' }, members: { type: [Schema.Types.ObjectId], ref: 'user' }, applicants: { type: [Schema.Types.ObjectId], ref: 'user' } }, details: { title: { type: String }, repository: { type: String }, description: { type: String }, tags: { type: [String] }, status: { type: String, enum: ['Active', 'Abandoned'], default: 'Active', required: true }, options: { language: { type: String }, timezone: { type: String }, max_members: { type: Number, default: 4 } } } }
Я боялся дублирования данных, поэтому я заставлял свои проекты указывать на пользователей, которые в них участвовали, вместо того, чтобы привязывать каждый проект к пользователю. Вместо этого мне следовало подумать о том, как пользователь взаимодействует с приложением.
Как пользователь, я хочу видеть проекты, в которых я участвую. Поэтому было бы гораздо лучше привязать идентификаторы проекта к пользователю, а затем заполнить его, когда я запрашиваю объект пользователя. Вместо этого я решил смоделировать свой проект отдельно от пользователя, что сделало необходимым выполнить три отдельных запроса по всей коллекции, чтобы определить, владельцем каких проектов я являюсь, участником или кандидатом.
3. Я не планировал состояние приложения.
Органически выращенное приложение - ужасная вещь. Это как незапланированный город. После нескольких часов добавления новых частей в состояние вы понимаете, что имеете тот же метод обработки входных изменений для ваших пользовательских опций и для создания вашего проекта.
export function handleChange (event) { return { type: types.INPUT_CHANGE, payload: event.target.value, name: event.target.name } } export function handleUserOptions (event) { return { type: types.USER_OPTION, payload: event.target.value, name: event.target.name } }
Причина, по которой я решил сделать что-то подобное (что пугает любого, кто верит в код D.R.Y.), заключается в том, что мне нужно было загрузить параметры из профиля пользователя и установить их в качестве значения в элементе выбора. Я не мог сделать это с помощью существующего метода, потому что он был настолько тесно связан с частью состояния проекта, что мне пришлось бы переписывать свой редуктор. Поэтому я решил сделать его хуже, продублировав код!
Заключительное слово
Это лишь некоторые из множества ужасов, которые я узнал о собственном коде. Если вы хотите увидеть репо, посетите: https://github.com/mmhansen/pear.
В этом упражнении я увидел слабость моей первой интуиции об архитектуре приложения и управлении сложностью. Я надеюсь, что время и практика помогут мне лучше понять, как писать многоразовый, самоописываемый и красивый простой код.