Технологии1 阅读

MVP — это не компромисс, а более умный порядок разработки

Будучи инженерами, мы по своей природе любим «делать всё полностью»: проектировать элегантную архитектуру, абстрагировать общие компоненты, настраивать аутентификацию, CI/CD, полноценную систему дизайна... В итоге сроки выхода в продакшн постоянно откладываются.

Мышление MVP (Minimum Viable Product, минимально жизнеспособный продукт) по сути помогает вам проверить, стоит ли продолжать вкладываться в идею, с меньшими затратами.

Для разработчика это не «писать плохой код», а «принимать правильные инженерные решения в нужное время».

Сначала разберёмся: что такое MVP?

Более формальное определение: MVP — это версия продукта с минимальным функционалом, которая тем не менее может быть передана реальным пользователям для сбора обратной связи и проверки гипотез.

Здесь три ключевых момента:

Если упростить до одной фразы: с минимальным объёмом кода выяснить одну вещь — действительно ли кто-то хочет этим пользоваться.

Ключевое мышление MVP-разработки

С инженерной точки зрения мышление MVP можно разбить на несколько шагов:

  1. Определить «один вопрос», который нужно проверить

    Например: «Захочет ли пользователь использовать минималистичный онлайн-инструмент для заметок вместо того, чтобы писать самому себе в WeChat или отправлять по почте?»

  2. Оставить только те функции, которые необходимы для проверки этого вопроса

    Создание заметки, отображение списка, удаление одной — достаточно.

  3. Реализовать самым быстрым для вас способом

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

  4. Запустить в минимальном объёме и наблюдать за реальным использованием

    Сначала скинуть коллегам и друзьям, затем подумать о публичном запуске.

  5. Итерации на основе данных о поведении, а не на основе придуманных требований

    Смотрите, возвращаются ли люди, чтобы продолжать использовать, а не слушайте их слова «отлично, отлично».

Простое сравнение: два типичных пути инженеров

Путь не MVP (тот, по которому мы часто идём):

Путь MVP:

Архитектуру можно будет доделать позже, но если никто не захочет пользоваться, вы просто построили «пустой город».

Практический пример: создаём MVP заметок на Next.js

Ниже используем «онлайн-заметки / памятки» в качестве примера и пройдём весь путь MVP-мышления на Next.js (App Router).

1. Сначала определяем границы MVP

Намеренно сужаем требования до минимума:

Что пока не делаем:

В таких границах любой разработчик, знакомый с React, сможет сделать работающую версию за 1–2 дня.

2. Скелет проекта и базовая структура

Создаём проект App Router рекомендованным в официальной документации способом.

В терминале:

bash
npx create-next-app@latest note-mvp
# или выбрать TypeScript, App Router и т.д. по умолчанию
cd note-mvp
npm run dev

В структуре каталогов основным является каталог app/. [nextjs](https://nextjs.org/docs/app)

Мы можем организовать минимальную структуру так:

3. Пишем минималистичный API с App Router

В app/api/notes/route.ts сначала используем массив в памяти для имитации хранилища (в реальной среде можно заменить на SQLite / Supabase и т.д.).

ts
// app/api/notes/route.ts
import { NextResponse } from 'next/server'

type Note = {
  id: string
  content: string
  createdAt: string
}

let notes: Note[] = []

export async function GET() {
  return NextResponse.json(notes)
}

export async function POST(request: Request) {
  const { content } = await request.json()
  if (!content || typeof content !== 'string') {
    return new NextResponse('Invalid content', { status: 400 })
  }

  const note: Note = {
    id: crypto.randomUUID(),
    content,
    createdAt: new Date().toISOString(),
  }

  notes.unshift(note)
  return NextResponse.json(note, { status: 201 })
}

export async function DELETE(request: Request) {
  const { searchParams } = new URL(request.url)
  const id = searchParams.get('id')
  if (!id) {
    return new NextResponse('Missing id', { status: 400 })
  }

  notes = notes.filter((n) => n.id !== id)
  return new NextResponse(null, { status: 204 })
}

В этом коде много «мест для оптимизации», например:

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

4. Пишем простую страницу

В app/page.tsx используем Client Component для обработки взаимодействия.

tsx
'use client'

import { useEffect, useState } from 'react'

type Note = {
  id: string
  content: string
  createdAt: string
}

export default function Home() {
  const [notes, setNotes] = useState<Note[]>([])
  const [input, setInput] = useState('')
  const [loading, setLoading] = useState(false)

  useEffect(() => {
    fetch('/api/notes')
      .then((res) => res.json())
      .then((data) => setNotes(data))
  }, [])

  async function handleAdd(e: React.FormEvent) {
    e.preventDefault()
    if (!input.trim()) return

    setLoading(true)
    try {
      const res = await fetch('/api/notes', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ content: input.trim() }),
      })
      const note: Note = await res.json()
      setNotes((prev) => [note, ...prev])
      setInput('')
    } finally {
      setLoading(false)
    }
  }

  async function handleDelete(id: string) {
    await fetch(`/api/notes?id=${id}`, { method: 'DELETE' })
    setNotes((prev) => prev.filter((n) => n.id !== id))
  }

  return (
    <main style={{ maxWidth: 600, margin: '2rem auto', padding: '0 1rem' }}>
      <h1>Minimal Notes MVP</h1>

      <form onSubmit={handleAdd} style={{ marginBottom: '1rem' }}>
        <textarea
          value={input}
          onChange={(e) => setInput(e.target.value)}
          rows={3}
          style={{ width: '100%', marginBottom: '0.5rem' }}
          placeholder="Напишите что-нибудь..."
        />
        <button type="submit" disabled={loading}>
          {loading ? 'Сохранение...' : 'Сохранить'}
        </button>
      </form>

      <section>
        {notes.length === 0 && <p>Пока нет заметок.</p>}
        {notes.map((note) => (
          <article
            key={note.id}
            style={{
              border: '1px solid #ddd',
              padding: '0.75rem',
              marginBottom: '0.75rem',
            }}
          >
            <p style={{ whiteSpace: 'pre-wrap' }}>{note.content}</p>
            <small style={{ color: '#666' }}>
              {new Date(note.createdAt).toLocaleString()}
            </small>
            <div>
              <button
                onClick={() => handleDelete(note.id)}
                style={{ marginTop: '0.25rem' }}
              >
                Удалить
              </button>
            </div>
          </article>
        ))}
      </section>
    </main>
  )
}

Эта страница очень «простая»: один textarea, одна кнопка, один список.

Но она уже замыкает полный цикл:

С точки зрения MVP этого уже достаточно, чтобы дать попробовать 5–10 тестовым пользователям и посмотреть на их реальное поведение.

Какое мышление MVP демонстрирует этот пример на Next.js?

Оглядываясь на эту реализацию, мы можем увидеть следующее:

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

Хотя сейчас используется хранение в памяти, пути API уже зафиксированы; в будущем достаточно изменить реализацию в route, чтобы перейти на базу данных.

«Захочет ли кто-то написать короткую заметку в браузере и вернуться на тот же вход для просмотра/удаления?»

Для инженера, хорошо знакомого с Next.js, эта MVP-реализация может быть выполнена и развёрнута на Vercel за один день.

Отправляясь от этой точки, вы можете определять дальнейший путь на основе поведения пользователей, например:

Распространённая ошибка: принимать «инженерное совершенство» за цель

В реальных проектах многие инженеры относятся к такому MVP как к «временной игрушке» и испытывают внутреннее сопротивление:

Здесь два реальных соображения:

MVP не отрицает качество инженерии, а откладывает «качественную инженерию» до того момента, когда потребность будет подтверждена.

В заключение: MVP-мышление для инженеров

Можно рассматривать мышление MVP как своего рода управление инженерными рисками:

Для опытных разработчиков практический подход таков:

Поделиться

Поделиться

Поделиться этой статьей.