Обновить

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

Уровень сложностиПростой
Время на прочтение7 мин
Охват и читатели6.2K
Всего голосов 13: ↑10 и ↓3+9
Комментарии23

Комментарии 23

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

План по валу?

Вал по плану©

Больше говнокода богу говнокода! ©

Итоговое решение получилось коротким, четким. Использовались абстракции, паттерны. 

Паттерны и нейросеть может, это её работа. А вот мыслить на нужном уровне абстракций может только человек.

Сейчас, в эпоху ИИ, найти информацию стало куда легче, чем раньше

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

Нейросети в поисковиках ищут. Даже дают ссылки, где они нашли.

Потому что сейчас все начальники хотят, чтобы качесво как вручную, а объём как из ИИ без проверок.

Про архитектуру и абстракции - весьма спорно.

Задача небольшая, инкапсулирована внутри одного класса.

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

ИМХО, абстракции нужно добавлять в код по мере необходимости. Т.е. как только потребовалась доработка: мелкий рефакторинг и добавил нужную абстракцию.

Особенно фиговым решением считаю своими руками писать синглтон и фабрику. Либо Guice, либо Spring IoC, либо jackarta.inject, ну или через ServiceLoader на худой конец, либо пинать сеньёра велосипедостроения. Иначе техдолговая яма наступит гораздо раньше.

А джун с нейросеткой не теряет возможности развиваться. Либо самостоятельно (читая литературу), либо методом летающего ежа, на каждом Code Review. Бизнесу он пользу принёс, причём быстро.

Согласен. Тем более, что синглтон многие вообще в антипаттерны записывают.

Тут, в целом может быть и так, но вообще многое зависит от устоявшихся привычек, которые не факт, что будут полезны с приходом нейросетей. Например, одним из важных требований выдвигают поддержание кода. Очень хорошо это понимаю, отзывается болью от изменения плохо написанных проектов, неоптимальных решений. Когда кто-то быстренько написал что-то вроде работающее, а тебе потом надо что-то по мелочи изменить и ты тратишь в 10 раз больше времени, всё переписывая, это неприятно. Но это при условии что весь код пишет человек. Если код написала нейросеть и поддерживать тоже будет нейросеть, а если что, так и зарефакторит, то так ли уже важен этот критерий? Важны другие - производительность, стабильность. Они часто бывают связаны с поддерживаемостью. А иногда и наоборот - пишется что-то лучше поддерживаемое, но хуже в плане производительности. Руководителю же важны скорость разработки и просто выполнение задачи без сюрпризов в будущем. Но и с сюрпризами будет бороться нейросеть. Так что тут многое надо ещё переосмысливать в восприятии. Как многие до сих пор ожидают, что нейросети должны сразу писать идеальный код с первого промпта и такие "Ага-а-а, не может. На помойку её. Вот человек бы..." забывая, что любые сложные задачи итеративны и поэтапны.

то так ли уже важен этот критерий?

Это самый главный критерий. Ллм точно также нужен понятный и поддерживаемый код, как и человеку. Иначе она начнет путаться, исправлять одно и ломать другое. Для нее код это контекст. Если там лапша и тупость, она и будет продолжать писать лапшу и тупость. + неподдерживаемый нейрокод это сложное или даже невозможное ревью.

Частично согласен. Если значение задано 20 раз в разных файлах, то более высока вероятность для современных моделей, что что-то упустит, хотя уже лучше справляется чем раньше. Но часто под поддерживаемостью понимается, что логика попроще, пусть и за счёт эффективности, наглядность повыше. То есть код строится в угоду именно ограничений интеллекта, а не из соображений эффективности. Даже вот выделение в функцию кода требует накладных расходов как правило - работа со стеком и прочее. Где-то можно и повторить пару раз с разными параметрами. Но человеку проще формировать модель восприятия так, а модели может быть и всё равно. В конце концов главная метрика - эффективность решения поставленной задачи. И если она решает эффективнее человека, то какая разница какой там код? Если он для эффективного решения объективно выгоднее когда структурирован, так он и будет становиться таким по мере улучшения обучения моделей.

эффективность решения поставленной задачи

Поддерживаемый код является существенной частью эффективности.

если она решает эффективнее человека, то какая разница какой там код?

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

Поддерживаемый код является существенной частью эффективности.

Это не более чем технический параметр, мало кому интересный. Более того, если написание поддерживаемого кода стоит дороже, а на качество продукта это никак не влияет, только на степень понимания кода программистами, то этот параметр быстренько оптимизируется в сторону игнорирования. Сильно кто-то заботится о поддерживаемом машинном коде скомпилированной программы, например?

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

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

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

Ну вот даже когда мы готовимся к собесу с помощью ИИ и он нам выдает паттерны якобы самые частые, и в итоге мы по сути только запомнили шаблонные решения, наш мозг ничего нового не усвоил. Это деградация как по мне.
Обучение не должно быть легким, если обучение легкое - это не обучение.

Хотя кому я это говорю, я сам еще мал и глуп) полтора года мобилки изучаю от силы.

Я кстати удивился, когда увидел решение мидла, не думал, что он OCP нарушит, раз он дошел до такого уровня.

С посылом статьи на 100% согласен, к оформлению доказательств вопросы...

КОД каждого участника в студию!
А так субъективному оценочному суждению привык не доверять, уж извините.

И мидла незаслуженно унизили) Ему задачки закрывать надо, быстро и более менее качественно. Ну нет у него 2-3 часа на неспешные раздумия, на таких как он IT держится.

И по соотношениям время исполнения/качество/цена работы в час он явно будет впереди.

А этому в принципе не верю (Сеньор с нейросетью 10 минут Все преимущества, что и без нейросетей 9/10 Все преимущества, что и без нейросетей).
Нейросеть то та же. А значит выдаст тот же примитив. Ну и если сеньор за 9 минут 30 секунд реализовал все как боженька, то я Жанна Дарт Вейдер))))

Тут именно про то, что сеньор не просто увидел код и принял решение, а доводил его до приемлемого формата)

Я со всеми созванивался и все смотрел, попрошу скинуть код всех, если интересует )

Поразмышлял над вашей статьей еще раз.

Оформил экспериментус. Дипсику скормил хороший промпт и вашу задачу.

Вот что он мне выдал

Скрытый текст
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

// ---------- Request DTO ----------
class NotificationRequest {
    private final String requestId;
    private final NotificationType type;
    private final String recipient;
    private final String message;

    private NotificationRequest(Builder builder) {
        this.requestId = builder.requestId;
        this.type = builder.type;
        this.recipient = builder.recipient;
        this.message = builder.message;
    }

    public String getRequestId() { return requestId; }
    public NotificationType getType() { return type; }
    public String getRecipient() { return recipient; }
    public String getMessage() { return message; }

    public static class Builder {
        private String requestId;
        private NotificationType type;
        private String recipient;
        private String message;

        public Builder requestId(String requestId) { this.requestId = requestId; return this; }
        public Builder type(NotificationType type) { this.type = type; return this; }
        public Builder recipient(String recipient) { this.recipient = recipient; return this; }
        public Builder message(String message) { this.message = message; return this; }
        public NotificationRequest build() { return new NotificationRequest(this); }
    }
}

// ---------- Enum ----------
enum NotificationType {
    EMAIL,
    SMS,
    PUSH
}

// ---------- Validator ----------
interface Validator {
    boolean isValid(NotificationRequest request);
}

class EmailValidator implements Validator {
    @Override
    public boolean isValid(NotificationRequest request) {
        return request.getRecipient() != null && request.getRecipient().contains("@");
    }
}

class SmsValidator implements Validator {
    private static final int EXPECTED_DIGITS_AFTER_PLUS = 11;

    @Override
    public boolean isValid(NotificationRequest request) {
        String recipient = request.getRecipient();
        if (recipient == null) return false;
        if (!recipient.contains("+")) return false;
        int plusIndex = recipient.indexOf('+');
        String afterPlus = recipient.substring(plusIndex + 1);
        return afterPlus.length() == EXPECTED_DIGITS_AFTER_PLUS;
    }
}

class PushValidator implements Validator {
    @Override
    public boolean isValid(NotificationRequest request) {
        String msg = request.getMessage();
        return msg != null && !msg.trim().isEmpty();
    }
}

// ---------- Validator Factory ----------
class ValidatorFactory {
    private static final Map<NotificationType, Validator> VALIDATORS = new EnumMap<>(NotificationType.class);

    static {
        VALIDATORS.put(NotificationType.EMAIL, new EmailValidator());
        VALIDATORS.put(NotificationType.SMS, new SmsValidator());
        VALIDATORS.put(NotificationType.PUSH, new PushValidator());
    }

    public static Validator getValidator(NotificationType type) {
        Validator validator = VALIDATORS.get(type);
        if (validator == null) {
            throw new IllegalArgumentException("Unsupported notification type: " + type);
        }
        return validator;
    }
}

// ---------- Sender ----------
interface Sender {
    void send(NotificationRequest request);
}

class EmailSender implements Sender {
    @Override
    public void send(NotificationRequest request) {
        System.out.println("Sending EMAIL to " + request.getRecipient());
    }
}

class SmsSender implements Sender {
    @Override
    public void send(NotificationRequest request) {
        System.out.println("Sending SMS to " + request.getRecipient());
    }
}

class PushSender implements Sender {
    @Override
    public void send(NotificationRequest request) {
        System.out.println("Sending PUSH to " + request.getRecipient());
    }
}

// ---------- Sender Factory ----------
class SenderFactory {
    private static final Map<NotificationType, Sender> SENDERS = new EnumMap<>(NotificationType.class);

    static {
        SENDERS.put(NotificationType.EMAIL, new EmailSender());
        SENDERS.put(NotificationType.SMS, new SmsSender());
        SENDERS.put(NotificationType.PUSH, new PushSender());
    }

    public static Sender getSender(NotificationType type) {
        Sender sender = SENDERS.get(type);
        if (sender == null) {
            throw new IllegalArgumentException("Unsupported notification type: " + type);
        }
        return sender;
    }
}

// ---------- Main Service ----------
class NotificationService {
    private static final Logger LOGGER = Logger.getLogger(NotificationService.class.getName());

    private final Set<String> processedIds = ConcurrentHashMap.newKeySet();
    private int processedCount = 0;
    private int duplicateCount = 0;
    private int failedCount = 0;

    /**
     * Processes a single request: validates, checks for duplicates, and sends if valid.
     */
    public void send(NotificationRequest request) {
        // 1. Duplicate check
        if (request.getRequestId() == null) {
            // requestId null is considered invalid (will be counted as failed)
            // but we still need to process? We'll treat as failed, but not as duplicate.
            // However, duplicate check requires requestId; if null, we cannot track it.
            // According to spec: requestId == null is invalid, so we skip duplicate check.
            // We'll directly mark as failed.
            LOGGER.log(Level.WARNING, "Request with null requestId – marking as failed");
            failedCount++;
            return;
        }

        if (!processedIds.add(request.getRequestId())) {
            // Already processed
            duplicateCount++;
            LOGGER.log(Level.INFO, "Duplicate request skipped: {0}", request.getRequestId());
            return;
        }

        // 2. Common null checks (as per spec: requestId, recipient, message must not be null)
        if (request.getRecipient() == null || request.getMessage() == null) {
            failedCount++;
            LOGGER.log(Level.WARNING, "Request {0} failed: recipient or message is null", request.getRequestId());
            return;
        }

        // 3. Type‑specific validation
        Validator validator = ValidatorFactory.getValidator(request.getType());
        if (!validator.isValid(request)) {
            failedCount++;
            LOGGER.log(Level.WARNING, "Request {0} failed validation", request.getRequestId());
            return;
        }

        // 4. Send
        Sender sender = SenderFactory.getSender(request.getType());
        sender.send(request);
        processedCount++;
        LOGGER.log(Level.INFO, "Request {0} processed successfully", request.getRequestId());
    }

    /**
     * Processes a list of requests.
     */
    public void processAll(List<NotificationRequest> requests) {
        for (NotificationRequest req : requests) {
            send(req);
        }
    }

    /**
     * Prints the final statistics.
     */
    public void printStats() {
        System.out.println("Processed: " + processedCount);
        System.out.println("Duplicates skipped: " + duplicateCount);
        System.out.println("Failed: " + failedCount);
    }
}

// ---------- Main Demonstration ----------
public class Main {
    public static void main(String[] args) {
        NotificationService service = new NotificationService();

        List<NotificationRequest> requests = Arrays.asList(
                // Valid requests
                new NotificationRequest.Builder()
                        .requestId("1")
                        .type(NotificationType.EMAIL)
                        .recipient("user1@mail.com")
                        .message("Hello email")
                        .build(),
                new NotificationRequest.Builder()
                        .requestId("2")
                        .type(NotificationType.SMS)
                        .recipient("+79990001122")
                        .message("Hello sms")
                        .build(),
                new NotificationRequest.Builder()
                        .requestId("3")
                        .type(NotificationType.PUSH)
                        .recipient("device-123")
                        .message("Hello push")
                        .build(),
                // Duplicate of requestId "1"
                new NotificationRequest.Builder()
                        .requestId("1")
                        .type(NotificationType.EMAIL)
                        .recipient("duplicate@mail.com")
                        .message("Duplicate")
                        .build(),
                // Invalid: null requestId
                new NotificationRequest.Builder()
                        .requestId(null)
                        .type(NotificationType.EMAIL)
                        .recipient("nullid@mail.com")
                        .message("null id")
                        .build(),
                // Invalid: recipient null
                new NotificationRequest.Builder()
                        .requestId("4")
                        .type(NotificationType.EMAIL)
                        .recipient(null)
                        .message("no recipient")
                        .build(),
                // Invalid: email without '@'
                new NotificationRequest.Builder()
                        .requestId("5")
                        .type(NotificationType.EMAIL)
                        .recipient("invalid-email")
                        .message("bad email")
                        .build(),
                // Invalid: SMS without '+'
                new NotificationRequest.Builder()
                        .requestId("6")
                        .type(NotificationType.SMS)
                        .recipient("79990001122")
                        .message("no plus")
                        .build(),
                // Invalid: SMS with wrong length after '+'
                new NotificationRequest.Builder()
                        .requestId("7")
                        .type(NotificationType.SMS)
                        .recipient("+123456")
                        .message("short")
                        .build(),
                // Invalid: PUSH empty message
                new NotificationRequest.Builder()
                        .requestId("8")
                        .type(NotificationType.PUSH)
                        .recipient("device-456")
                        .message("")
                        .build()
        );

        service.processAll(requests);
        service.printStats();
    }
}


Теперь вывод: у сеньора промпт лучше. За 10 минут он еще кофе с сигаретой прикончить успел.

Полюбому

Оценку по времени давал не я, а они, так что чем он занимался остальное время, я не знаю))

И там же был смысл в том, чтобы добить нейронку до хорошего решения, я же не сказал, что там был 1 запрос)

С посылом статьи согласен, но вот с аргументацией как будто бы неочень. Я по бумагам синьор-помидор, и на жабе уже лет 10 пишу в энтерпрайзах различной величины и степени кровавости. Если бы я сейчас получил такую задачу, то я написал бы решение как в самом первом листинге. Потому что уже не раз видел, до чего доходит вся ваша "архитектура" с чистым кодом. На проектах 10+ лет оно превращается в бесконечное месиво из абстракций, у некоторых сущностей десятки уровней наследования, фабрики фабрик и прочее чистокодовое нечитаемое говно. Вот когда надо будет масштабировать (спойлер, в 99% случаев оказывается не надо), вот тогда отмасштабирую. А сейчас - самое простое и тупое школьное решение, которое можно легко окинуть взглядом и понять за секунду имея одну извилину.

А куда джунам деваться ? От них требуют умение пользоваться ИИ, иначе на работу не возьмут.

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

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

Увы. Все всегда хотят побольше и подешевле. До ИИ это было невозможно.

Сейчас ситуация изменилась. Один тратит 4 часа на задачу, второй справляется за 5 минут. Кого наймут? Даже если решение плохое, оно работает. Оно приносит деньги прямо сейчас.

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

До ИИ не было джунов, способных решать задачи за 5 минут. Поэтому бизнес процесс был подстроен под осознание этого факта.

Очень часто джун был бы рад, будь у него место в настоящей команде, где платят минимальные деньги (речь не о 30 000, а чтобы был свет, интернет и пачка сосисок с чаем) и ставили адекватные джуну задачи. Но он будет делать задачу 4 часа и практически всегда бизнесу выгоднее нанять оператора клода, а не классического джуна старого поколения. Классический джун со стороны бизнеса-это как платить за обучение студента без гарантии, что он останется работать у тебя. Вайбкодерский джун-это прото лоу грейд кодер, закрывающий задачи уже сейчас.

Я почитал бы остальные варианты кода, если они существовали.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации