소프트웨어의 중요성
소프트웨어의 사소한 결함이 목숨을 빼앗아 갈 수도 있음!
소프트웨어 개발보안: 안전한 소프트웨어 개발을 위해 보안을 고려하여 기능을 설계 및 구현하고 잠재적인 보안취약점(보안약점)을 제거하는 소프트웨어 개발 과정에서의 일련의 보안활동
SSDL: SDL 단계마다 필수적인 보안과 무결성을 보증하는 소프트웨어 방법론
보안약점 vs 보안 취약점
-보안약점(SW weekness): 해킹 등 실제 보안사고에 악용될 수 있는 보안 취약점의 근본 원인
-보안 취약점(SW Vulnerability): 소프트웨어 실행 시점에 보안약점이 원인이 되어 발생하는 실제적인 위협
안전한 소프트웨어: 보안 기능을 수행하는 소프트웨어 X, 시스템을 신뢰할 수 있는 상태로 유지할 수 있도록 만들어진 소프트웨어
#Rule 22. Minimize the scope of variables
-변수의 영역 범위를 최소화하라
-공통적인 프로그래밍 오류를 피할 수 있음. 코드의 가독성이 높아짐
- 사용되지 않은 변수들 쉽게 탐지 가능, 유지보수성 향상
-가비지 콜렉터가 좀 더 빠르게 객체 회수 가능
public class Scope{
public static void main(String[] args){
int i = 0;
for(i = 0; i < 10; i++){
// ...
}
//...
}
}
/*
문제점: i가 for문에서만 사용되는데 메서드 영역 내에서 선언됨
가능한 영역 범위를 최소화하는 것이 좋음
*/
Solution
for(int i = 0; i < 10; i++){
//...
}
public class Foo{
private int count;
public void counter(){
count = 0;
// ...
}
public boolean condition(){
//Not use count
}
//...
}
counter() 메서드에서만 사용되는 count 변수를 메서드 밖에서 선언함
-> 메서드를 복사하여 사용할 경우 재사용성 감소, 분석의 효율성도 감소
public void counter(){
int count;
//...
}
메서드의 지역변수로 선언하는 것이 적절
#Rule 23 Minimize the scope of the @SupperessWarnings annotation
-@SuppressWarnings 주석의 범위를 최소화하라
-@SuppressWarnings 주석은 Class, method, 변수 선언 등에 사용될 수 있음
-범위를 최소화해서 좀 더 작은 영역에 대한 경고만을 억제하도록 하는 것이 중요함
-미검사 경고는 코드가 타입-안전하다는 것이 보장되었을 때만 허용될 수 있음
@SupperessWarnings("unchecked") //전체 클래스에 대한 미검사 경고 억제
class Legacy{
Set s = new HashSet(); //제네릭을 사용 안 함
public final void doLogic(int a, char c){
s.add(a);
s.add(c); //Type-unsafe
}
}
나중에 ClassCastException이 발생할 수 있음
Solution
class Legacy{
@SupperessWarnings("unchecked")
Set s = new HashSet();
public final void doLogic(int a, char c){
s.add(a);
s.add(c);
}
}
-범위를 줄이지 못 하면 런타임 예외가 발생할 수 있고 타입-안전성을 보장하지 못할 수 있음
*타입 안정성을 높인다는 것은 의도하지 않는 타입의 객체가 저장되는 것을 막고, 저장된 객체를 꺼내올 때 원래의 타입과 다른 타입으로 잘못 형변환되어 발생할 수 있는 오류를 줄어준다는 것이다.
#Rule 24. Minimize the accessibility of classes and their members
-클래스와 멤버들에 대한 접근성을 최소화하라
-접근 수정자를 통해 클래스와 클래스 멤버들(클래스, 인터페이스, 필드, 메서드)에 대해 접근성을 지정할 수 있음
*접근 수정자
private: 클래스 내에서만 접근 가능
default: 같은 패키지 내에서만 접근 가능
protected: 같은 패키지와 하위 클래스에서만 접근 가능
public: 모두 접근 가능
-최소한의 접근성만을 허용하여 악성코드가 안정성을 훼손할 수 있는 기회를 최소화해야 함
-데이터 멤버들을 private로 선언하고 엑세스 가능한 래퍼 메서드를 제공하라, 필드는 private 선언 권고
Pulbic final class Point{
private final int x;
public Point(int x){ //pulbic 생성자를 사용하면 변수를 private으로 선언 한 의미가 없어짐
this.x = x;
}
public int getX(){
return x;
}
}
비신뢰 코드가 Point 인스턴스를 생성하고 private 변수인 x에 접근할 수 있음
Solution 1. public 메서드를 가진 final 클래스
*최상위 클래스는 private으로 선언할 수 없음
final class Point{
private final int x;
Point(int x){
this.x =x;
}
public int GetX{
return x;
}
}
#Solution 26. Always provide feedback about the resulting of a method
-메서드 결과값에 대해 항상 피드백을 제공하라
-클라이언트가 올바른 결과와 그렇지 않은 결과를 구분할 수 있도록 하고 어떠한 올바르지 않은 결과도 신중하게 처리할 수 있도록 지원해야 함
-유효한 값 vs 오류 값 vs 예외
-모호한 반환값 사용 x
pubic void updateNode(int id, int newValue){
Node current = root;
while(current != null){
if(current.getId() == id ){
current.setValue(newValue); //여기서 오류가 발생할 가능성도 있음;
break; //어떤 노드를 수정했는지, 수정이 일어났는지, 성공했는지 알 수 없음
}
current = current.next;
}
}
적절한 반환값이 없어 수정이 일어났는지, 어느 노드가 수정됐는지, 성공했는지 여부를 알 수 없다.
Solution1. Boolean
pubic boolean updateNode(int id, int newValue){
Node current = root;
while(current != null){
if(current.getId() == id ){
current.setValue(newValue); //여기서 오류가 발생할 가능성도 있음;
return true; //수정됨
}
current = current.next;
}
return false;
}
Solution2. 수정된 노드 반환, Null 반환값
pubic Node updateNode(int id, int newValue){
Node current = root;
while(current != null){
if(current.getId() == id ){
current.setValue(newValue); //여기서 오류가 발생할 가능성도 있음;
return current;
}
}
return null;
}
*인밴드 표시자, NullPointerException이 발생할 수 있음
Solution3. Exception
pubic Node updateNode(int id, int newValue) throws NodeNotFoundException{
Node current = root;
while(current != null){
if(current.getId() == id ){
current.setValue(newValue); //여기서 오류가 발생할 가능성도 있음;
return current;
}
}
throw new NodeNotFoundException(); //예외처리
}
*예외처리가 항상 적절한 것은 아님
#Rule 28. Do not attach significance to the ordinal associated with an enum
-enum에서 부여한 순서번호에 의미를 두지 말라
*enum: 자바의 열거형 타입, 열거 상수 위치 번호를 반환하는 ordinal()이라는 메서드를 가진다
-enum 상수의 ordinal()애 의미를 부여하면 오류를 발생시킬 수 있음
-ordinal()값은 프로그래머가 세팅할 수 없는 값이기 때문에 사용을 지양해야 함
enum HydroCarbon{
METHANE, ETHANE, PROPANE, BUTANE, PENTANE, HEXANE, HEPTANE, OCTANE, NONANE, DECANE;
public int getNumberOfCarbons(){
return ordinal() + 1;
}
}
순서가 변경되거나 새로운 상수가 추가되면 유지보수에 문제가 있음
일반적으로 정수 값을 유도해내기 위해 순서번호를 이용하는 것은 프로그램의 유지보수성을 감소시키고 오류를 초래할 수 있음
enum HydroCarbon{
METHANE(1), ETHANE(2), PROPANE(3), BUTANE(4), PENTANE(5), HEXANE(6), BENZENE(6), HEPTANE(7), OCTANE(8),
NONANE(9), DECANE(10);
private int numOfCarbon;
private HydroCarbon(int n){
numOfCarbon = n;
}
public int getnumOfCarbon(){
return numOfCarbon;
}
}
#Rule 29. Be aware of numeric promotion behavior
-숫자의 확대변환 동작을 주의하라
*숫자의 확대변환: 산술 연산자의 피연산자들을 공통 타입으로 변환해서 연산이 수행될 수 잇도록 하기 위한 것
1. 피연산자들이 레퍼런스 타입이면 언박싱 수행
2. double로 변환
3. float로 변환
4. long
5. int
-정수에서 실수로의 변환은 정확도를 떨어뜨릴 수 있음
int x = 214783642
x += 1.0f
위 연산식에서는 연산을 수행하기 전에 int에서 float으로 확대변환이 일어난다. 그 과정에서 float의 유효숫자 범위를 넘어가기 때문에 반올림이 되어 정확하지 않은 숫자로 변환된다. 따라서 결과값은 정확도가 떨어지는 값이 나옴
double x = 21474836442;
x += 1.0;
피연산자를 double 타입으로 바꾸면 유효범위를 넘어가지 않음
cf) memory를 아껴야 하는 프로그램이라면 정확도가 좀 떨어지더라도 float을 사용하는 방법을 선택할 수 있음
#Rule 30 Enable compile-time type checking of variable arity parameter types
-가변형 매개변수의 타입에 대해 컴파일 검사를 시행하라
-가변형 매개변수로 Object나 제네릭 타입이 사용될 경우 컴파일 때 타입 검사를 하는 것은 비효율적
-메서드의 매개변수에 가장 가능성 있는 특정 타입을 사용함으로써 컴파일 시에 가변형 메서드에 대한 강력한 타입 검사를 수행하도록 하라
-런타임 에러 처리보다 컴파일시 걸러주는 것이 더 안정적임
<T> double sum(T... args){
//...
}
제네릭 타입으로 받았기 때문에 컴파일러가 Number 타입이 아닌 매개변수를 걸러주지 못하기 때문에 비효율적이다.
<T extends Number> double sum(T... args){
//...
}
Number타입을 상속받은 제네릭 클래스로 구체화하여 런타임 오류 발생 가능성을 줄였다.
#Rule 31. Do not apply public final to constants whose value might change in later release
-이후의 릴리즈에서 변경될 수 있는 상수에 public final을 사용하지 말라
-public final 필드를 읽는 어떤 컴파일 단위에서도 그 필드 값을 인라인으로 삽입하도록 허용
-클래스 선언이 수정되어 필드에 다른 값을 주게 되어도 public final 필드값을 읽는 컴파일 단위는 재컴파일 될 때까지 여전히 이전 값으로 인식
-참조한 코드를 재컴파일하지 않으면 변경사항이 적용되지 않음
-수학 상수가 아니라면 static final 클래스 변수로 선언하지 않는 것이 좋음
class Foo{
public static final int version = 1;
}
class Bar{
...{
System.out.println(Foo.version);
}
}
Foo의 version이라는 필드가 변경되어도 Bar를 재컴파일 하지 않으면 올바르지 못한 값을 출력한다.
class Foo{
private static int version = 1;
public static final int getVersion(){
return version;
}
}
class Bar{
...{
System.out.println(Foo.getVersion());
}
}
#Rule 33. Prefer user-defined exceptions over more general exception types
-일반적인 예외 타입 보다는 사용자 -정의 예외를 사용하라
-예외는 그 타입에 의해서 포착됨
-특정한 목적의 예외를 정의하는 것이 나음
-일반적인 예외 타입을 발생시키는 것은 코드를 이해하고 유지하기 어렵게 만들고, 예외처리 메커니즘의 장점을 무산시킴
*Exception은 CheckedException과 UncheckedException이 있음. RunTimeException은 UncheckedException임
-try-catch문으로 구체적인 예외를 따로 처리하는 것이 좋음
try{
doSomething();
}catch(Throwable e){ //모든 예외가 하나의 catch문으로 잡힘
switch(e.getMessage()){
case "file not found":
//...
break;
//...
default: throw e;
}
}
에러 메세지가 변경되면 의도대로 예외처리가 작동하지 않음
public class TimeoutException extends Exception{
//...
}
//...
try{
}catch(TimeoutException te){
//...
}catch(FileNotFoundException fe){
//...
}
구체적인 사용자 정의 예외를 정의하고 사용하여 적절하게 예외처리를 할 수 있음
#Rule 37:Do not shadow or obscure identifiers in subscopes
-부분영역의 식별자들을 섀도잉하거나 차폐하지 말라
*Obscure: 차폐현상
단순이름이 패키지명, 타입(클래스)명 혹은 변수명으로 해석될 수 있는 문맥에서 타입보다는 변수명으로 패키지보다는 타입명으로 해석됨
*Shadowing: 섀도잉 현상
지역변수가 해당 영역에서 이름이 같은 다른 변수에 대한 접근을 불가하게 만듬
-어떤 식별자도 그것을 포함하고 있는 영역에서 다른 식별자를 차폐하거나 섀도잉해서는 안된다.
class MyVector{
private int val = 1;
public void doLogic(){
int val; //메서드 변수가 인스턴스 변수 이름을 가림
}
}
doLogic() 함수에서는 똑같은 이름의 변수를 사용하였기 때문에 인스턴스 변수 val에 접근할 수 없다.
class MyVector{
private int val = 1;
private void doLogic(){
int newValue;
}
}
변수의 이름을 다르게 설정하면 필드 섀도잉 현상을 피할 수 있다.
'프로그래밍 > Java Programming' 카테고리의 다른 글
JDBC Programming (0) | 2022.12.10 |
---|