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.
// 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.
// 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.
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:
validateAppointmentenotifySuccessmantê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
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
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
// 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>
);
}// 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.
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.
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.
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
- Múltiplas responsabilidades: Ambos os exemplos fazem muitas coisas diferentes (UI, API, Negócio, Hardware, Formatação).
- 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. - Falta de Hooks Internos: Toda a lógica de estado e handlers deveria estar em um hook, deixando o componente/tela limpo.
- Difícil de testar: Funções longas exigem mocks complexos e possuem muitos caminhos de execução (complexidade ciclomática alta).
- Pouca legibilidade: É difícil entender o que a função faz "de relance".
- Manutenção Arriscada: Alterar a lógica de formatação do relatório pode quebrar acidentalmente o tratamento de erro da API.
- 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.