Skip to content

Funções Curtas e de Responsabilidade Única

Manter as funções curtas e com responsabilidade única é uma prática essencial para facilitar a manutenção, legibilidade e testabilidade do código. Cada função deve se concentrar em um único objetivo e evitar realizar múltiplas tarefas. Abaixo estão exemplos reais aplicados ao contexto de Telemedicina usando React e TypeScript.

1. Responsabilidade Única

Cada função deve se concentrar em uma única responsabilidade. Em componentes React, isso se traduz em manter o JSX limpo e delegar toda a lógica para hooks internos.

Exemplo: Agendamento de Consulta

Neste exemplo, o componente de tela não possui lógica interna; ele apenas consome um hook que gerencia o estado e as ações.

tsx
// AppointmentScheduler.screen.tsx
import React from 'react';
import { useAppointmentScheduler } from './AppointmentScheduler.hooks';

function AppointmentScheduler() {
    const { loading, handleSchedule } = useAppointmentScheduler();

    return (
        <button onClick={handleSchedule} disabled={loading}>
            {loading ? 'Agendando...' : 'Agendar Consulta'}
        </button>
    );
}

Hook de Lógica Interna

Toda a lógica de validação e disparo de ações é extraída para um hook específico.

tsx
// AppointmentScheduler.hooks.ts
import { useDispatch, useSelector } from 'react-redux';
import { appointmentActions } from './store';

export function useAppointmentScheduler() {
    const dispatch = useDispatch();
    const { loading, currentData } = useSelector((state: RootState) => state.appointments);

    function handleSchedule() {
        if (validateAppointment(currentData)) {
            dispatch(appointmentActions.requestSchedule(currentData));
        }
    }

    function validateAppointment(data: AppointmentData) {
        if (!data.doctorId || !data.date) {
            alert("Dados de agendamento incompletos");
            return false;
        }
        return true;
    }

    return {
        loading,
        handleSchedule
    };
}

Camada de Efeito (Redux-Saga)

Na nossa arquitetura, a lógica assíncrona e chamadas de API ficam isoladas nos Sagas.

typescript
import {call, put, takeLatest} from 'redux-saga/effects';

function* handleScheduleSaga(action: ReturnType<typeof appointmentActions.requestSchedule>) {
    try {
        yield call(api.post, '/appointments', action.payload);
        yield put(appointmentActions.scheduleSuccess());
        yield call(notifySuccess);
    } catch (error) {
        yield put(appointmentActions.scheduleFailure(error.message));
    }
}

function notifySuccess() {
    alert("Consulta agendada com sucesso!");
}

export function* appointmentSaga() {
    yield takeLatest(appointmentActions.requestSchedule.type, handleScheduleSaga);
}

Neste exemplo:

  • Componente: Apenas dispara uma intenção (dispatch) e valida dados de entrada simples.
  • Saga: Centraliza o fluxo de efeitos colaterais (API, notificações, sucessos/falhas).
  • Funções auxiliares: validateAppointment e notifySuccess mantêm suas responsabilidades únicas.

2. Handler de Erros

O tratamento de exceções deve ser isolado para não poluir a lógica principal de negócio.

Exemplo: Inicialização de Chamada de Vídeo

typescript
function useVideoCall() {
    async function startCall(roomId: string) {
        try {
            await initializeMediaDevices();
            await connectToRoom(roomId);
        } catch (error) {
            logAndReportError(error);
        }
    }

    async function initializeMediaDevices() {
        return await navigator.mediaDevices.getUserMedia({video: true, audio: true});
    }

    function logAndReportError(error: unknown) {
        const message = error instanceof Error ? error.message : "Erro desconhecido";
        console.error(`[VideoCall Error]: ${message}`);
        // Integração com serviço de monitoramento (ex: Sentry)
        MonitoringService.captureException(error);
    }

    return {startCall};
}

3. Handler de Comportamento

Lógicas de decisão ou regras de negócio complexas devem ser extraídas para funções ou hooks que lidam apenas com esse comportamento.

Exemplo: Verificação de Elegibilidade de Retorno

typescript
function useConsultationRules() {
    function isEligibleForReturn(lastConsultationDate: Date): boolean {
        const daysSinceLastVisit = calculateDaysDifference(new Date(), lastConsultationDate);
        return daysSinceLastVisit <= 30;
    }

    function calculateDaysDifference(date1: Date, date2: Date): number {
        const diffInTime = date1.getTime() - date2.getTime();
        return Math.ceil(diffInTime / (1000 * 3600 * 24));
    }

    return {isEligibleForReturn};
}

4. Handler de Eventos

Interações do usuário devem ser processadas de forma independente. O componente deve apenas mapear eventos para funções fornecidas pelo hook de lógica.

Exemplo: Chat durante Teleconsulta

tsx
// ConsultationChat.screen.tsx
import React from 'react';
import { useConsultationChat } from './ConsultationChat.hooks';

function ConsultationChat() {
    const { message, onChangeMessage, onSendMessage } = useConsultationChat();

    return (
        <form onSubmit={onSendMessage}>
            <input value={message} onChange={onChangeMessage} />
            <button type="submit">Enviar</button>
        </form>
    );
}
tsx
// ConsultationChat.hooks.ts
import { useState } from 'react';

export function useConsultationChat() {
    const [message, setMessage] = useState("");

    function onSendMessage(e: React.FormEvent) {
        e.preventDefault();
        
        if (isMessageValid(message)) {
            dispatchMessage(message);
            clearInput();
        }
    }

    function onChangeMessage(e: React.ChangeEvent<HTMLInputElement>) {
        setMessage(e.target.value);
    }

    function isMessageValid(text: string) {
        return text.trim().length > 0;
    }

    function dispatchMessage(text: string) {
        chatSocket.emit("send_message", { text, timestamp: new Date() });
    }

    function clearInput() {
        setMessage("");
    }

    return {
        message,
        onChangeMessage,
        onSendMessage
    };
}

5. Testabilidade

Ao quebrar a lógica em funções pequenas, podemos testar comportamentos específicos sem a necessidade de renderizar o componente completo ou mockar todo o ambiente.

typescript
describe("useConsultationRules", () => {
    it("deve permitir retorno se a última consulta foi há menos de 30 dias", () => {
        const {isEligibleForReturn} = useConsultationRules();
        const recentDate = new Date();
        recentDate.setDate(recentDate.getDate() - 15);

        expect(isEligibleForReturn(recentDate)).toBe(true);
    });

    it("não deve permitir retorno se a última consulta foi há mais de 30 dias", () => {
        const {isEligibleForReturn} = useConsultationRules();
        const oldDate = new Date();
        oldDate.setDate(oldDate.getDate() - 40);

        expect(isEligibleForReturn(oldDate)).toBe(false);
    });
});

Benefícios

  • Facilidade de Teste: Funções e hooks isolados são testados mais rapidamente.
  • Reutilização: Hooks e funções utilitárias podem ser compartilhados entre diferentes telas da aplicação de Telemedicina.
  • Legibilidade: O componente de tela torna-se extremamente simples, servindo apenas como uma declaração da UI.
  • Manutenibilidade: Alterações na lógica não afetam a estrutura visual e vice-versa.

Aqui está um exemplo de código ruim, onde um único componente faz muitas coisas diferentes e ignora a separação de lógica para hooks, dificultando a manutenção em um cenário de Telemedicina.

Exemplos de Código Ruim (Telemedicina)

1. Componente Monolítico (Várias Responsabilidades)

Neste exemplo, o componente lida com hardware, chamadas de API, UI, logs e erros tudo no mesmo arquivo, sem usar hooks internos ou Sagas.

tsx
function BadTelemedicineRoom({ roomId }) {
    const [status, setStatus] = useState("idle");

    async function joinRoom() {
        try {
            console.log("Iniciando...");
            const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
            
            if (stream) {
                const response = await fetch(`https://api.telemed.com/rooms/${roomId}/join`, {
                    method: 'POST',
                    body: JSON.stringify({ token: '123' })
                });

                if (response.ok) {
                    setStatus("connected");
                    alert("Conectado com sucesso!");
                } else {
                    console.error("Erro na API");
                    setStatus("error");
                }
            }
        } catch (e) {
            console.error("Erro fatal", e);
            setStatus("error");
        }
    }

    return (
        <div>
            <p>Status: {status}</p>
            <button onClick={joinRoom}>Entrar na Chamada</button>
        </div>
    );
}

2. Função "Linguiceiro" (Função Gigante e Complexa)

Este é um exemplo de função que não segue o princípio de Funções Curtas. Ela tenta processar todo o encerramento de uma consulta em um único bloco de código.

tsx
function handleFinishConsultation(consultationId: string, data: any, doctor: any) {
    // 1. Validação de dados pesada
    if (!data.diagnosis) {
        alert("O diagnóstico é obrigatório");
        return;
    }
    if (data.prescriptions.length === 0) {
        const confirm = window.confirm("Deseja encerrar sem prescrições?");
        if (!confirm) return;
    }

    // 2. Formatação de objeto complexo
    const finalReport = {
        id: consultationId,
        doctorName: doctor.name,
        specialty: doctor.specialty,
        timestamp: new Date().toISOString(),
        content: data.diagnosis,
        medications: data.prescriptions.map((p: any) => ({
            name: p.name,
            dosage: p.dosage,
            period: `${p.days} dias`
        })),
        isEmergency: data.priority === 'high'
    };

    // 3. Lógica de negócio condicional
    if (finalReport.isEmergency) {
        console.log("Notificando hospital de retaguarda...");
        // Simulação de chamada de socket/api
        api.post('/notify-emergency', { consultationId });
    }

    // 4. Chamada de API direta (Violação de Arquitetura)
    fetch(`/api/consultations/${consultationId}/finish`, {
        method: 'POST',
        body: JSON.stringify(finalReport)
    })
    .then(res => res.json())
    .then(result => {
        // 5. Manipulação de UI e efeitos colaterais após API
        if (result.success) {
            alert("Consulta finalizada!");
            window.location.href = "/dashboard";
        } else {
            console.error("Erro ao salvar");
        }
    })
    .catch(err => {
        // 6. Tratamento de erro misturado
        MonitoringService.send(err);
        alert("Erro crítico");
    });
}

Problemas com esses Códigos

  1. Múltiplas responsabilidades: Ambos os exemplos fazem muitas coisas diferentes (UI, API, Negócio, Hardware, Formatação).
  2. Violação da Arquitetura: Chamadas de API (fetch) e lógica de negócio assíncrona devem estar no Saga, não no componente ou handler de tela.
  3. Falta de Hooks Internos: Toda a lógica de estado e handlers deveria estar em um hook, deixando o componente/tela limpo.
  4. Difícil de testar: Funções longas exigem mocks complexos e possuem muitos caminhos de execução (complexidade ciclomática alta).
  5. Pouca legibilidade: É difícil entender o que a função faz "de relance".
  6. Manutenção Arriscada: Alterar a lógica de formatação do relatório pode quebrar acidentalmente o tratamento de erro da API.
  7. Código Duplicado: Lógicas como a formatação do relatório ou tratamento de erros de API tendem a ser repetidas em outras funções se não forem extraídas.