문제

나는 데이터베이스 연결을 생성하는 데 사용되는 작은 루틴으로 작업하고 있었습니다.

전에

public DbConnection GetConnection(String connectionName)
{
   ConnectionStringSettings cs= ConfigurationManager.ConnectionStrings[connectionName];
   DbProviderFactory factory = DbProviderFactories.GetFactory(cs.ProviderName);
   DbConnection conn = factory.CreateConnection();
   conn.ConnectionString = cs.ConnectionString;
   conn.Open();

   return conn;
}

그런 다음 .NET 프레임워크 문서를 조사하기 시작했습니다. 문서화 다양한 일의 행동을 보고, 내가 그것을 처리할 수 있는지 확인합니다.

예를 들어:

ConfigurationManager.ConnectionStrings...

그만큼 선적 서류 비치 전화한다고 하네 연결 문자열 던진다 구성오류예외 컬렉션을 검색할 수 없는 경우.이 경우에는 이 예외를 처리하기 위해 제가 할 수 있는 일이 없으므로 그대로 두겠습니다.


다음 부분은 실제 인덱싱입니다. 연결 문자열 찾다 연결 이름:

...ConnectionStrings[connectionName];

이 경우에는 ConnectionStrings 문서 그 재산이 돌아올 것이라고 말한다. 없는 연결 이름을 찾을 수 없는 경우.이런 일이 발생하는지 확인하고 누군가가 잘못된 연결 이름을 제공했다는 사실을 알리기 위해 예외를 발생시킬 수 있습니다.

ConnectionStringSettings cs= 
      ConfigurationManager.ConnectionStrings[connectionName];
if (cs == null)
   throw new ArgumentException("Could not find connection string \""+connectionName+"\"");

나는 다음과 같은 운동을 반복합니다:

DbProviderFactory factory = 
      DbProviderFactories.GetFactory(cs.ProviderName);

그만큼 GetFactory 메소드에는 지정된 팩토리가 있는 경우 어떤 일이 발생하는지에 대한 문서가 없습니다. ProviderName 찾을 수 없습니다.반환한다는 내용이 문서화되어 있지 않습니다. null, 하지만 난 여전히 방어적일 수 있고 확인하다 null의 경우:

DbProviderFactory factory = 
      DbProviderFactories.GetFactory(cs.ProviderName);
if (factory == null) 
   throw new Exception("Could not obtain factory for provider \""+cs.ProviderName+"\"");

다음은 DbConnection 개체의 구성입니다.

DbConnection conn = factory.CreateConnection()

다시 선적 서류 비치 연결을 만들 수 없으면 어떻게 되는지 말하지 않지만 다시 null 반환 개체를 확인할 수 있습니다.

DbConnection conn = factory.CreateConnection()
if (conn == null) 
   throw new Exception.Create("Connection factory did not return a connection object");

다음은 Connection 개체의 속성을 설정하는 것입니다.

conn.ConnectionString = cs.ConnectionString;

문서에서는 연결 문자열을 설정할 수 없는 경우 어떤 일이 발생하는지 언급하지 않습니다.예외가 발생합니까?그것을 무시합니까?대부분의 예외와 마찬가지로 연결의 ConnectionString을 설정하는 동안 오류가 발생한 경우 이를 복구하기 위해 할 수 있는 일은 없습니다.그래서 나는 아무것도 하지 않을 것이다.


마지막으로 데이터베이스 연결을 엽니다.

conn.Open();

그만큼 개방형 방식 DbConnection은 추상적이므로 어떤 예외를 발생시킬지 결정하는 것은 DbConnection의 후손인 공급자에 달려 있습니다.또한 추상 Open 메소드 문서에는 오류가 있을 경우 발생할 수 있는 상황에 대한 지침이 없습니다.연결하는 동안 오류가 발생했다면 처리할 수 없다는 것을 압니다. 호출자가 사용자에게 일부 UI를 표시하고 다시 시도할 수 있도록 버블링을 허용해야 합니다.


후에

public DbConnection GetConnection(String connectionName)
{
   //Get the connection string info from web.config
   ConnectionStringSettings cs= ConfigurationManager.ConnectionStrings[connectionName];

   //documented to return null if it couldn't be found
    if (cs == null)
       throw new ArgumentException("Could not find connection string \""+connectionName+"\"");

   //Get the factory for the given provider (e.g. "System.Data.SqlClient")
   DbProviderFactory factory = DbProviderFactories.GetFactory(cs.ProviderName);

   //Undefined behaviour if GetFactory couldn't find a provider.
   //Defensive test for null factory anyway
   if (factory == null)
      throw new Exception("Could not obtain factory for provider \""+cs.ProviderName+"\"");

   //Have the factory give us the right connection object
   DbConnection conn = factory.CreateConnection();

   //Undefined behaviour if CreateConnection failed
   //Defensive test for null connection anyway
   if (conn == null)
      throw new Exception("Could not obtain connection from factory");

   //Knowing the connection string, open the connection
   conn.ConnectionString = cs.ConnectionString;
   conn.Open()

   return conn;
}

요약

그래서 내 4줄 기능은 12줄이 되었고 문서 조회에 5분의 시간이 필요했습니다.결국 나는 메소드가 null을 반환하도록 허용되는 경우를 발견했습니다.그러나 실제로 내가 한 일은 액세스 위반 예외(널 참조에 대해 메서드를 호출하려고 시도하는 경우)를 InvalidArgumentException.

나는 또한 발생할 수 있는 두 가지 경우를 포착합니다. 없는 객체 반환;하지만 다시 한 번 예외를 다른 예외로 교환했습니다.

긍정적인 측면에서는 두 가지 문제를 포착하고 앞으로 나쁜 일이 발생하기보다는 예외 메시지에서 발생한 일을 설명했습니다(예:책임은 여기서 멈춥니다)

하지만 그만한 가치가 있나요?이건 과잉인가요?이 방어 프로그래밍이 잘못 되었나요?

도움이 되었습니까?

해결책

수동으로 구성을 확인하고 예외를 발생시키는 것은 구성이 누락된 경우 프레임워크에서 예외를 발생시키도록 하는 것보다 나을 것이 없습니다.어쨌든 프레임워크 메서드 내에서 발생하는 전제 조건 검사를 복제하는 것일 뿐이며 이로 인해 아무런 이점도 없이 코드가 장황해지게 됩니다.(실제로 당신은 그럴 수도 있습니다. 풀이 모든 것을 기본 Exception 클래스로 던져 정보를 수집합니다.프레임워크에서 발생하는 예외는 일반적으로 더 구체적입니다.)

편집하다:이 답변은 다소 논란의 여지가 있는 것으로 보이므로 약간의 설명이 필요합니다.방어 프로그래밍은 "예상치 못한 상황에 대비"(또는 "편집증")을 의미하며 이를 수행하는 방법 중 하나는 전제 조건을 많이 확인하는 것입니다.대부분의 경우 이는 좋은 관행이지만 모든 관행과 마찬가지로 비용은 이점과 비교하여 평가되어야 합니다.

예를 들어 "공장에서 연결을 얻을 수 없습니다." 예외를 발생시키는 것은 어떤 이점도 제공하지 않습니다. 공급자를 얻을 수 없습니다. 공급자가 null이면 바로 다음 줄에서 예외가 발생합니다.따라서 전제 조건 확인 비용(개발 시간 및 코드 복잡성 측면에서)은 타당하지 않습니다.

반면에 연결 문자열 구성이 있는지 확인하는 검사입니다. 5월 예외는 개발자에게 문제 해결 방법을 알려주는 데 도움이 될 수 있으므로 정당합니다.어쨌든 다음 줄에 표시되는 null 예외는 누락된 연결 문자열의 이름을 알려주지 않으므로 전제 조건 확인이 일부 값을 제공합니다.예를 들어 코드가 구성 요소의 일부인 경우 구성 요소 사용자가 구성 요소에 필요한 구성을 알 수 없으므로 값이 상당히 큽니다.

방어 프로그래밍에 대한 다른 해석은 오류 조건을 감지하는 것뿐만 아니라 발생할 수 있는 오류나 예외로부터 복구도 시도해야 한다는 것입니다.나는 이것이 일반적으로 좋은 생각이라고 생각하지 않습니다.

기본적으로 가능한 예외만 처리해야 합니다. 하다 어떤것에 대하여.어쨌든 복구할 수 없는 예외는 최상위 수준 처리기로 위쪽으로 전달되어야 합니다.웹 애플리케이션에서 최상위 핸들러는 아마도 일반 오류 페이지만 표시할 것입니다.그러나 데이터베이스가 오프라인이거나 일부 중요한 구성이 누락된 경우 대부분의 경우 실제로 수행할 작업이 많지 않습니다.

이러한 종류의 방어적 프로그래밍이 적합한 경우는 사용자 입력을 수락하는 경우이며 해당 입력으로 인해 오류가 발생할 수 있습니다.예를 들어 사용자가 URL을 입력으로 제공하고 애플리케이션이 해당 URL에서 무언가를 가져오려고 시도하는 경우 URL이 올바른지 확인하고 요청으로 인해 발생할 수 있는 모든 예외를 처리하는 것이 매우 중요합니다.이를 통해 사용자에게 귀중한 피드백을 제공할 수 있습니다.

다른 팁

글쎄, 그것은 청중이 누구인지에 달려 있습니다.

당신이 그것을 사용하는 방법에 대해 당신에게 이야기하지 않을 많은 다른 사람들에 의해 사용될 것으로 예상되는 라이브러리 코드를 작성한다면, 그것은 과잉이 아닙니다.그들은 당신의 노력에 감사할 것입니다.

(즉, 그렇게 하는 경우 System.Exception보다 더 나은 예외를 정의하여 일부 예외는 포착하고 다른 예외는 포착하지 않으려는 사람들이 더 쉽게 만들 수 있도록 제안합니다.)

그러나 만약 당신이 그것을 직접 사용한다면(또는 당신과 당신의 친구), 분명히 그것은 과잉이며 아마도 코드를 읽기 어렵게 만들어 결국 당신에게 해를 끼칠 것입니다.

우리 팀이 이런 코드를 작성할 수 있었으면 좋겠습니다.대부분의 사람들은 방어 프로그래밍의 요점조차 이해하지 못합니다.그들이 하는 최선의 방법은 전체 메소드를 try catch 문으로 감싸고 모든 예외가 일반 예외 블록에 의해 처리되도록 하는 것입니다!

이안에게 경의를 표합니다.나는 당신의 딜레마를 이해할 수 있습니다.나도 같은 일을 겪었습니다.하지만 당신이 한 일은 아마도 일부 개발자가 몇 시간 동안 키보드를 두드리는 데 도움이 되었을 것입니다.

.net 프레임워크 API를 사용할 때 무엇을 기대하시나요?무엇이 자연스러워 보입니까?코드에서도 동일한 작업을 수행하세요.

시간이 걸린다는 걸 알아요.하지만 품질에는 비용이 따릅니다.

추신:실제로 모든 오류를 처리하고 사용자 정의 예외를 발생시킬 필요는 없습니다.귀하의 방법은 다른 개발자들만 사용할 것임을 기억하십시오.일반적인 프레임워크 예외를 스스로 파악할 수 있어야 합니다.그것은 수고할 가치가 없습니다.

귀하의 "이전" 예는 명확하고 간결하다는 특징이 있습니다.

뭔가 잘못되면 결국 프레임워크에서 예외가 발생합니다.예외에 대해 아무것도 할 수 없다면 예외가 호출 스택 위로 전파되도록 놔두는 것이 좋습니다.

그러나 실제 문제가 무엇인지 실제로 밝히지 못하는 프레임워크 내부 깊숙한 곳에서 예외가 발생하는 경우가 있습니다.문제가 유효한 연결 문자열이 없지만 프레임워크에서 "잘못된 null 사용"과 같은 예외를 발생시키는 것이라면 때로는 예외를 포착하고 더 의미 있는 메시지와 함께 다시 발생시키는 것이 더 좋습니다.

작업할 실제 객체가 필요하고 객체가 비어 있으면 던져지는 예외가 최소한으로 기울어지기 때문에 null 객체를 많이 확인합니다.그러나 나는 그것이 일어날 일이라는 것을 아는 경우에만 null 객체를 확인합니다.일부 개체 팩토리는 null 개체를 반환하지 않습니다.대신 예외가 발생하며 이러한 경우 null을 확인하는 것은 쓸모가 없습니다.

나는 널 참조 검사 논리를 작성하지 않을 것이라고 생각합니다. 적어도 여러분이 했던 방식은 아닙니다.

응용 프로그램 구성 파일에서 구성 설정을 가져오는 내 프로그램은 시작 시 해당 설정을 모두 확인합니다.나는 일반적으로 설정을 포함하고 해당 클래스의 속성을 참조하는 정적 클래스를 만듭니다. ConfigurationManager) 응용 프로그램의 다른 곳.여기에는 두 가지 이유가 있습니다.

첫째, 애플리케이션이 제대로 구성되지 않으면 작동하지 않습니다.나는 나중에 데이터베이스 연결을 생성하려고 할 때보다 프로그램이 구성 파일을 읽는 순간 이것을 알고 싶습니다.

둘째, 구성의 유효성을 확인하는 것은 실제로 구성에 의존하는 개체의 관심사가 되어서는 안 됩니다.이미 검사를 수행한 경우 코드 전체에 검사를 삽입하는 것은 의미가 없습니다.(물론 여기에는 예외가 있습니다. 예를 들어, 프로그램이 실행되는 동안 구성을 변경하고 해당 변경 사항이 프로그램 동작에 반영되도록 해야 하는 장기 실행 애플리케이션의 경우;이와 같은 프로그램에서는 ConfigurationManager 설정이 필요할 때마다.)

나는 null 참조 검사를 수행하지 않을 것입니다. GetFactory 그리고 CreateConnection 전화도 그렇고.해당 코드를 실행하기 위해 테스트 케이스를 어떻게 작성하시겠습니까?당신은 그 메소드가 null을 반환하도록 만드는 방법을 모르기 때문에 그렇게 할 수 없습니다. 가능한 해당 메서드가 null을 반환하도록 합니다.따라서 한 가지 문제를 대체했습니다. 프로그램에서 다음과 같은 오류가 발생할 수 있습니다. NullReferenceException 당신이 이해하지 못하는 조건 하에서 – 또 다른 더 중요한 조건으로:이와 동일한 불가사의한 조건에서 귀하의 프로그램은 테스트하지 않은 코드를 실행하게 됩니다.

내 경험 법칙은 다음과 같습니다.

의 메시지가 표시되면 잡지 마십시오. throw된 예외는 방문객.

따라서 NullReferenceException에는 관련 메시지가 없으므로 null인지 확인하고 더 나은 메시지로 예외를 발생시킵니다.ConfigurationErrorException은 관련이 있으므로 잡을 수 없습니다.

유일한 예외는 GetConnection의 "계약"이 구성 파일에서 반드시 연결 문자열을 검색하지 않는 경우입니다.

GetConnection이 연결을 검색할 수 없다는 사용자 정의 예외와 계약을 체결해야 하는 경우 사용자 정의 예외에 ConfigurationErrorException을 래핑할 수 있습니다.

다른 해결 방법은 GetConnection이 throw할 수 없지만(그러나 null을 반환할 수 있음) 클래스에 "ExceptionHandler"를 추가하도록 지정하는 것입니다.

분석법 문서가 누락되었습니다.;-)

각 메소드에는 정의된 입력 및 출력 매개변수와 정의된 결과 동작이 있습니다.귀하의 경우에는 다음과 같습니다."성공한 경우 유효한 열린 연결을 반환하고, 그렇지 않으면 null을 반환합니다(또는 원하는 대로 XXXException을 발생시킵니다).이 동작을 염두에 두고 이제 프로그래밍 방법을 방어적으로 결정할 수 있습니다.

  • 메소드가 실패 이유와 실패에 대한 자세한 정보를 노출해야 하는 경우 이전처럼 수행하고 모든 것을 확인하고 포착하여 적절한 정보를 반환하십시오.

  • 그러나 개방형 DBConnection에만 관심이 있거나 단지 없는 (또는 일부 사용자 정의 예외) 실패 시 모든 것을 try/catch로 래핑하고 반환합니다. 없는 (또는 일부 예외) 오류 발생 시, 객체 else.

그래서 나는 그것이 메소드의 동작과 예상되는 출력에 달려 있다고 말하고 싶습니다.

일반적으로 데이터베이스 관련 예외는 포착되어 (가설)과 같은 좀 더 일반적인 것으로 다시 발생되어야 합니다. DataAccessFailure 예외.대부분의 경우 상위 수준 코드는 데이터베이스에서 데이터를 읽고 있다는 사실을 알 필요가 없습니다.

이러한 오류를 신속하게 잡아내는 또 다른 이유는 메시지에 "해당 테이블이 없습니다.ACCOUNTS_BLOCKED' 또는 '사용자 키가 잘못되었습니다.234234".이것이 최종 사용자에게 전파되면 여러 면에서 좋지 않습니다.

  1. 혼란스럽다.
  2. 잠재적인 보안 침해.
  3. 회사 이미지에 당황스럽습니다(고객이 조악한 문법으로 된 오류 메시지를 읽는다고 상상해 보십시오).

첫 번째 시도와 똑같이 코딩했을 것입니다.

그러나 해당 함수의 사용자는 USING 블록을 사용하여 연결 개체를 보호합니다.

나는 다른 버전처럼 예외를 번역하는 것을 정말 좋아하지 않습니다. 왜 그렇게 하면 오류가 발생했는지 알아내기가 매우 어렵기 때문입니다(데이터베이스가 다운되었나요?구성 파일 등을 읽을 수 있는 권한이 없습니까?)

수정된 버전은 애플리케이션에 다음과 같은 기능이 있는 한 많은 가치를 추가하지 않습니다. AppDomain.UnexpectedException 덤프하는 핸들러 exception.InnerException 체인 및 모든 스택 추적을 일종의 로그 파일(또는 더 나은 방법으로 미니덤프 캡처)에 저장한 다음 호출합니다. Environment.FailFast.

추가 오류 검사로 메서드 코드를 복잡하게 만들 필요 없이 해당 정보를 통해 무엇이 잘못되었는지 정확히 찾아내는 것이 합리적으로 간단합니다.

처리하는 것이 바람직하다는 점을 참고하세요. AppDomain.UnexpectedException 그리고 전화해 Environment.FailFast 최상위 레벨을 갖는 대신 try/catch (Exception x) 후자의 경우 추가 예외로 인해 문제의 원래 원인이 모호해질 수 있기 때문입니다.

이는 예외를 포착하면 열려 있는 모든 항목이 finally 블록이 실행되고 더 많은 예외가 발생하여 원래 예외가 숨겨집니다(또는 더 나쁜 경우에는 일부 상태를 되돌리려는 시도로 파일, 잘못된 파일, 심지어 중요한 파일까지 삭제할 수도 있습니다).처리 방법을 모르는 잘못된 프로그램 상태를 나타내는 예외를 포착해서는 안 됩니다. 심지어 최상위 수준에서도 마찬가지입니다. main 기능 try/catch 차단하다.손질 AppDomain.UnexpectedException 그리고 전화 Environment.FailFast 다른(그리고 더 바람직한) 물고기 주전자입니다. finally 실행을 차단하고, 더 이상의 손상을 입히지 않고 프로그램을 중지하고 유용한 정보를 기록하려는 경우 프로그램을 실행하고 싶지 않을 것입니다. finally 블록.

OutOfMemoryExceptions를 확인하는 것을 잊지 마세요...알잖아, 그거 ~할 것 같다 일어나다.

Iain의 변화는 제가 보기엔 합리적으로 보입니다.

시스템을 사용하고 있는데 부적절하게 사용하는 경우 오용에 대한 최대한의 정보를 원합니다.예:메소드를 호출하기 전에 구성에 일부 값을 삽입하는 것을 잊어버린 경우 KeyNotFoundException/NullReferenceException이 아닌 내 오류를 자세히 설명하는 메시지와 함께 InvalidOperationException을 원합니다.

그것은 컨텍스트 IMO에 관한 것입니다.나는 내 시간에 상당히 뚫을 수 없는 예외 메시지를 본 적이 있지만 프레임워크에서 나오는 기본 예외는 완벽하게 괜찮습니다.

일반적으로, 특히 다른 사람이 많이 사용하는 내용을 작성하거나 일반적으로 오류를 진단하기 어려운 호출 그래프의 깊은 곳에 있는 내용을 작성할 때는 실수에 주의하는 것이 더 낫다고 생각합니다.

나는 코드나 시스템의 개발자로서 단지 그것을 사용하는 사람보다 실패를 진단하는 데 더 나은 위치에 있다는 것을 항상 기억하려고 노력합니다.몇 줄의 코드 확인과 사용자 정의 예외 메시지가 누적적으로 디버깅 시간을 절약할 수 있는 경우가 있습니다(그리고 문제를 디버깅하기 위해 다른 사람의 컴퓨터로 끌려갈 필요가 없기 때문에 자신의 삶도 더 쉽게 만들 수 있습니다).

내 눈에는 당신의 "이후" 샘플이 실제로 방어적인 것은 아닙니다.왜냐하면 방어는 당신이 통제할 수 있는 부분을 확인하는 것이기 때문입니다. connectionName (null 또는 비어 있는지 확인하고 ArgumentNullException을 발생시킵니다.)

방어 프로그래밍을 모두 추가한 후 기존 방법을 분리해 보는 것은 어떨까요?별도의 방법을 보장하는 여러 가지 고유한 논리 블록이 있습니다.왜?그러면 함께 속한 논리를 캡슐화하고 결과 공개 메서드가 해당 블록을 올바른 방식으로 연결하기 때문입니다.

이와 유사한 것(SO 편집기에서 편집되었으므로 구문/컴파일러 검사가 없습니다.컴파일되지 않을 수도 있습니다 ;-))

private string GetConnectionString(String connectionName)
{

   //Get the connection string info from web.config
   ConnectionStringSettings cs= ConfigurationManager.ConnectionStrings[connectionName];

   //documented to return null if it couldn't be found
   if (cs == null)
       throw new ArgumentException("Could not find connection string \""+connectionName+"\"");
   return cs;
}

private DbProviderFactory GetFactory(String ProviderName)
{
   //Get the factory for the given provider (e.g. "System.Data.SqlClient")
   DbProviderFactory factory = DbProviderFactories.GetFactory(ProviderName);

   //Undefined behaviour if GetFactory couldn't find a provider.
   //Defensive test for null factory anyway
   if (factory == null)
      throw new Exception("Could not obtain factory for provider \""+ProviderName+"\"");
   return factory;
}

public DbConnection GetConnection(String connectionName)
{
   //Get the connection string info from web.config
   ConnectionStringSettings cs = GetConnectionString(connectionName);

   //Get the factory for the given provider (e.g. "System.Data.SqlClient")
   DbProviderFactory factory = GetFactory(cs.ProviderName);


   //Have the factory give us the right connection object
   DbConnection conn = factory.CreateConnection();

   //Undefined behaviour if CreateConnection failed
   //Defensive test for null connection anyway
   if (conn == null)
      throw new Exception("Could not obtain connection from factory");

   //Knowing the connection string, open the connection
   conn.ConnectionString = cs.ConnectionString;
   conn.Open()

   return conn;
}

추신:이것은 완전한 리팩터링이 아니며 처음 두 블록만 수행했습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top