No hay forma de requerir una implementación para lanzar una excepción a través de una interfaz, incluso en lenguajes como Java donde puede declarar que un método podría arrojar una excepción.
Puede haber una manera de asegurar (hasta cierto punto, pero no del todo) que se produce una excepción. Puede crear una implementación abstracta de su interfaz. Luego puede implementar el GetUsermétodo como final en la clase abstracta y usar el patrón de estrategia para llamar a otro miembro protegido de la subclase y lanzar una excepción si devuelve algo que no sea un usuario válido (como nulo). Esto todavía puede caerse si, por ejemplo, el otro desarrollador devuelve un tipo de objeto nulo User, pero realmente tendrían que trabajar para subvertir la intención aquí. También podrían simplemente reimplementar su interfaz, también mala, por lo que puede considerar reemplazar la interfaz por completo con la clase abstracta.
(Se pueden lograr resultados similares usando la delegación en lugar de subclasificar con algo como un decorador de envoltura).
Otra opción podría ser crear un conjunto de pruebas de conformidad que todo el código de implementación debe pasar para ser incluido. La eficacia de esto depende de cuánto control tenga sobre el enlace del otro código al suyo.
También estoy de acuerdo con otros en que la documentación y la comunicación son claras cuando se espera un requisito como este, pero no se puede aplicar por completo en el código.
Ejemplos de código:
Método de subclase:
public abstract class ExceptionalUserRepository : IUserRepository
{
public sealed User GetUser(int user_id)
{
User u = FindUserByID(user_id);
if(u == null)
{
throw new UserNotFoundException();
}
return u;
}
// subclasses implement this method instead
protected abstract User FindUserByID(int user_id);
// More code here
}
Método decorador:
public sealed class DecoratedUserRepository : IUserRepository
{
private readonly IUserRepository _userRepository;
public DecoratedUserRepository(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public User GetUser(int user_id)
{
User u = _userRepository.GetUser(user_id);
if(u == null)
{
throw new UserNotFoundException();
}
return u;
}
// More code here
}
public class SomeClass
{
private readonly IUserRepository _userRepository;
// They now *have* to pass in exactly what you want
public SomeClass(DecoratedUserRepository userRepository)
{
_userRepository = userRepository;
}
// More code
}
Un último punto rápido que quiero señalar que olvidé antes es que al hacer cualquiera de estos, se está vinculando a una implementación más específica, lo que significa que los desarrolladores implementadores obtienen mucha menos libertad.