You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
El proyecto funciona y tiene 279 tests en verde, pero acumuló restos de
refactors anteriores: código de un proveedor que ya no existe, constantes que
nadie lee, y varias piezas donde una clase hace tres trabajos a la vez. Nada de
esto rompe nada hoy; todo encarece cada cambio futuro.
Auditoría completa de los 9 módulos de producción (~1.100 líneas). Cada punto
verificado con grep sobre el árbol, no supuesto.
1. Código muerto
utils/checks.py → bot_in_voice() — Ningún cog lo usa. Sus únicas
referencias son los dos tests que lo prueban, así que el suite cubre una
función que nadie llama.
track["spotify_url"] — El soporte de Spotify se eliminó y _build_track
nunca escribe esa clave, pero quedan dos lecturas vivas
(utils/embeds.py:12, cogs/music.py:116) con un or que siempre es falso.
cogs/lyrics.py:38 — title.split(" — ")[0] existe porque los títulos de
Spotify venían como "Canción — Artista". Sin Spotify, ese split ahora
corta títulos legítimos de YouTube que contengan un guion largo.
track["stream"] — _build_track la fija a None en cada track y ningún
módulo la lee jamás. El streaming real pasa por AudioStream.
config.COOKIES_PATH — Se define pero nadie la importa: services/media.py
lee os.getenv("COOKIES_PATH") por su cuenta. Dos dueños para un mismo ajuste,
y el módulo de configuración deja de ser la fuente de verdad.
2. Violaciones SOLID
SRP — MusicPlayer hace tres trabajos. Es el motor de reproducción, pero
además construye embeds de Discord (_load_error_embed) y publica mensajes en
el canal (_safe_send, _idle_disconnect). Un cambio de redacción obliga a
tocar el núcleo de audio.
DIP — dependencia circular MusicPlayer ↔ players.destroy() llama al
singleton global players.discard(...), de modo que la clase depende del
registro que la contiene. Instanciar un player en un test arrastra el estado
global del proceso.
Encapsulación rota.PlayerManager.get_or_create lee player._destroyed,
un atributo privado de otra clase.
Abstracción con fugas.get_or_create(bot, ctx) recibe un Context de
discord.py entero solo para sacar ctx.guild.id y ctx.channel. El gestor de
players queda atado al framework de comandos sin necesitarlo.
OCP — añadir un efecto obliga a editar tres sitios. El filtro va en EFFECTS, el texto en _LABELS y además hay que escribir un método de comando
casi idéntico a los otros ocho. Dos diccionarios con las mismas claves que
pueden desincronizarse en silencio, y 60 líneas que solo cambian en el nombre.
!help duplica el registro de comandos.main.py lista los comandos a
mano; los cogs son los que realmente los definen. Ya hay deriva: !effect
existe y no aparece en la ayuda.
3. Complejidad y duplicación
utils/checks.py:_error es idéntica a utils.embeds.error_embed. Misma
función, dos implementaciones, dos sitios que cambiar si cambia el color.
cogs/lyrics.py:52 define un @lyrics.error que responde ctx.send(str(error)). Tiene dos efectos malos: expone texto crudo de
excepción al usuario, y como utils/errors.handle se rinde cuando ve has_error_handler(), desactiva el manejo central de errores para todo el
cog de letras — justo lo que el PR Answer every wrong command invocation #16 construyó.
services/lyrics_api.py:47 usa print() en vez de logging. Esquiva la
configuración de logs y no llega a journalctl con nivel ni timestamp, contra
la regla de logging del CLAUDE.md.
services/lyrics_api.py construye un cliente de Genius nuevo en cada
consulta.
Comentarios redundantes que repiten lo que la línea ya dice
(source.cleanup() # stop FFmpeg) y banners decorativos.
deploy/launch_ec2.sh se queda. La EC2 está detenida, no terminada, y deploy/README.md la documenta como camino de despliegue alterno. Es
herramienta de infraestructura vigente, no código muerto.
Criterio de aceptación
Los 279 tests siguen en verde, sin relajar ninguna aserción.
Ningún cambio de comportamiento observable para el usuario, salvo dos
correcciones deliberadas: !help pasa a incluir !effect, y los errores de !lyrics dejan de mostrar texto crudo de excepción.
El proyecto funciona y tiene 279 tests en verde, pero acumuló restos de
refactors anteriores: código de un proveedor que ya no existe, constantes que
nadie lee, y varias piezas donde una clase hace tres trabajos a la vez. Nada de
esto rompe nada hoy; todo encarece cada cambio futuro.
Auditoría completa de los 9 módulos de producción (~1.100 líneas). Cada punto
verificado con
grepsobre el árbol, no supuesto.1. Código muerto
utils/checks.py→bot_in_voice()— Ningún cog lo usa. Sus únicasreferencias son los dos tests que lo prueban, así que el suite cubre una
función que nadie llama.
track["spotify_url"]— El soporte de Spotify se eliminó y_build_tracknunca escribe esa clave, pero quedan dos lecturas vivas
(
utils/embeds.py:12,cogs/music.py:116) con unorque siempre es falso.cogs/lyrics.py:38—title.split(" — ")[0]existe porque los títulos deSpotify venían como
"Canción — Artista". Sin Spotify, esesplitahoracorta títulos legítimos de YouTube que contengan un guion largo.
track["stream"]—_build_trackla fija aNoneen cada track y ningúnmódulo la lee jamás. El streaming real pasa por
AudioStream.config.COOKIES_PATH— Se define pero nadie la importa:services/media.pylee
os.getenv("COOKIES_PATH")por su cuenta. Dos dueños para un mismo ajuste,y el módulo de configuración deja de ser la fuente de verdad.
2. Violaciones SOLID
MusicPlayerhace tres trabajos. Es el motor de reproducción, peroademás construye embeds de Discord (
_load_error_embed) y publica mensajes enel canal (
_safe_send,_idle_disconnect). Un cambio de redacción obliga atocar el núcleo de audio.
MusicPlayer↔players.destroy()llama alsingleton global
players.discard(...), de modo que la clase depende delregistro que la contiene. Instanciar un player en un test arrastra el estado
global del proceso.
PlayerManager.get_or_createleeplayer._destroyed,un atributo privado de otra clase.
get_or_create(bot, ctx)recibe unContextdediscord.py entero solo para sacar
ctx.guild.idyctx.channel. El gestor deplayers queda atado al framework de comandos sin necesitarlo.
EFFECTS, el texto en_LABELSy además hay que escribir un método de comandocasi idéntico a los otros ocho. Dos diccionarios con las mismas claves que
pueden desincronizarse en silencio, y 60 líneas que solo cambian en el nombre.
!helpduplica el registro de comandos.main.pylista los comandos amano; los cogs son los que realmente los definen. Ya hay deriva:
!effectexiste y no aparece en la ayuda.
3. Complejidad y duplicación
utils/checks.py:_errores idéntica autils.embeds.error_embed. Mismafunción, dos implementaciones, dos sitios que cambiar si cambia el color.
cogs/lyrics.py:52define un@lyrics.errorque respondectx.send(str(error)). Tiene dos efectos malos: expone texto crudo deexcepción al usuario, y como
utils/errors.handlese rinde cuando vehas_error_handler(), desactiva el manejo central de errores para todo elcog de letras — justo lo que el PR Answer every wrong command invocation #16 construyó.
services/lyrics_api.py:47usaprint()en vez delogging. Esquiva laconfiguración de logs y no llega a
journalctlcon nivel ni timestamp, contrala regla de logging del
CLAUDE.md.services/lyrics_api.pyconstruye un cliente de Genius nuevo en cadaconsulta.
(
source.cleanup() # stop FFmpeg) y banners decorativos.Fuera de alcance (deliberado)
_PLAYER_CLIENTS,AudioStream(stderr a archivo, no a pipe),BufferedAudioSourceyaresampledocumentan bugs que ya costaron los PRsMake nightcore and vaporwave rate-independent #17, Use YouTube player clients that are not bot-checked #25, Read ahead so a stalled source cannot speed up playback #28 y Stop treating an unfilled buffer as the end of the track #30. Borrarlos invita a reintroducirlos.
deploy/launch_ec2.shse queda. La EC2 está detenida, no terminada, ydeploy/README.mdla documenta como camino de despliegue alterno. Esherramienta de infraestructura vigente, no código muerto.
Criterio de aceptación
correcciones deliberadas:
!helppasa a incluir!effect, y los errores de!lyricsdejan de mostrar texto crudo de excepción.