За последнюю неделю я потратил около 24 часов на создание приложения под названием «Груша». За эти дни кодовая база выросла почти до 4 тысяч строк. В моем последнем более крупном проекте, копии веб-сайта «Люди Нью-Йорка», я хотел сделать все идеально, когда я только выпустил его. Я, наверное, потратил 200 часов в течение месяца, чтобы попытаться усовершенствовать каждую функцию, но когда я перешел в рабочий режим, все эти новые проблемы возникли. Так что на этот раз я подумал: ну, черт возьми, я просто сначала сделаю доказательство концепции, а затем постараюсь улучшить это.

Поэтому я решил погрузиться в свой проект, сохранив краткий список задач на доске проектов github, а остальная часть плана витала в моей голове. Как вы думаете, где это меня? Это привело меня к воскресному утру, где я смотрю программу, о которой трудно рассуждать. Тот, который поражает меня каждым новым компонентом.

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

  1. Я не видел основной структуры того, что на самом деле является просто "грязным" приложением.

Идея проекта - создать инструмент для совместной работы начинающих программистов. Я хотел, чтобы у пользователей была возможность (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.

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