화면을 개발할 때 직접 코드를 다 작성하는 경우도 있지만, 경우에 따라 이미 만들어진 라이브러리를 import 해서 구현을 할 때가 있다. 그런데 라이브러리 컴포넌트 UI가 마음에 들지 않아 변경하려고 style을 추가했는데도 전혀 적용이 되지 않는 경우가 있었다. F12를 눌러 개발자도구로 확인했을 때에는 내가 작성한게 하나도 적용이 되지 않고,, 구글링부터 ChatGPT까지 이런 경우에는 어떻게 해야하는지 알아본 과정을 기록하는 포스팅이다.

 

 

1. querySelector() + !important

설정 style이 적용이 안되자마자 바로 적용했던 방법이다. 클래스명이나 id명으로 element를 가져온 후, 해당 요소의 스타일 속성을 !important로 설정하는 것이다. 가끔 다른 설정 때문에 내가 작성한 설정이 적용 안될 때 쓰는 방법이다.

 

  • 예시
  • 상황 설명 : 요소에 설정한 클래스명이 example-element이고, 이 클래스를 적용한 요소들의 height를 500px이 되게 고정하고 싶은 경우
// 1. 자바스크립트에서 설정 
const element = document.querySelector('example-element');
if (element) {
    element.style.setProperty('height', '500px', 'important');
 }
 
 2. css에서 설정
 .exampleElement { height : 500px !important;}

 

-> 이렇게 설정한 후 웹 개발자 도구를 확인한 결과, 여전히 exampleElement 클래스를 설정한 요소의 height는 500px이 아닌 다른 값이었다. 

 

2. 코드 실행 시점을 바꾸기

라이브러리를 이용하다보니 내가 작성한 코드가 라이브러리 코드 전에 먼저 실행되기 때문에 내가 작성한 코드가 덮어씌어져서 적용이 안되는 걸 수도 있다고 한다. 그래서 addEventListener를 이용해 코드 적용 시점을 바꿔보려고 했었다.

 

방법은 위 1번에서 작성한 코드를 window.addEventListener() 안에 넣어서 실행하면 된다.

window.addEventListener('load', function() {
    const element = document.querySelector('.example-element');
    if (element) {
      element.style.setProperty('height', '500px', 'important');
    }
}

-> 이렇게 했음에도 여전히 example-element 클래스를 적용한 요소의 height: 500px은 적용이 되지 않았다... 

 

3.  MutationObserver로 감시해서 강제로 바꾸기

그렇게 chatGPT에게 물어보고 시도해본 결과.. 방법을 찾았는데, 바로 MutationObserver의 observe() 함수를 이용하는 것이었다.

이건 '누가 height를 바꾸면 다시 내가 덮어씌우겠다' 라는 전략이라고 한다. 결국 라이브러리에서 요소의 height를 바꾸는 순간이 있을텐데, 그 순간을 감지한 후 내가 원하는 style을 설정하는 방법이다.

const target = document.querySelector('.example-element');
if (target) {
    const observer = new MutationObserver(() => {
        target.style.setProperty('height', '500px', 'important');
    });
    
    observer.observe(target, {attributes: true, attributeFilter : ['style']});
    
    
    // 처음에도 한 번 설정 
    target.style.setProperty('height', '500px', 'important');
}

-> 이렇게 설정해주니 내가 의도한 .example-element 클래스를 적용한 요소의 height가 500px로 고정되는 것을 화면에서 확인할 수 있었다..!

 

앞선 1, 2번과 다른 점은 new MutationObserver()라는 객체를 생성한 뒤 .observe()를 호출했다는 것이다.

observe()의 인수에 대해 설명해보자면 다음과 같다.

observer.observe(target, {
                  attributes: true // 속성(attribute)의 변경 감지
                 ,attributeFilter : ['style'] // style 속성만 감지
                 });

observer.observe(target, options)
1. target : 감시할 DOM 요소(.example-element)
2. options : 어떤 변경을 감시할 건지 설정하는 객체
  - options 상세
    - attributes : 요소의 속성(attribute)이 변경될 때 감지할지 여부(true/false)
    - attributeFilter : 감시할 속성 이름 배열(['style']이면 style의 변경만 감지)

 

 

라이브러리를 사용하면 간편하게 구현을 할 수 있다는 장점이 있지만, 어떠한 메커니즘으로 돌아가는 코드인지 정확히 잘 모르기 때문에 다루기가 쉽지는 않은 것 같다. 꼭 바꿔야 하는 경우라면 observe를 적용하는 것이 방법이 될 것 같다.

요약

재3자가 확장할 수 없는 클래스라면 가능한한 직렬화 프록시 패턴을 사용하자

  • 직렬화 프록시 패턴 사용 불가능한 경우
    • 클라이언트가 클래스 확장할 수 있는 경우
    • 객체 그래프에 순환이 있는 클래스

직렬화 프록시 패턴을 사용하면 쉽게 중요한 불변식을 안정적으로 직렬화 할 수 있을 것이다.

 

1. 직렬화 프록시 패턴(Serialization Proxy Pattern)

Serializable을 구현하기로 했다면, 일반적인 인스턴스 생성 방법인 생성자 이외의 방법으로 인스턴스를 생성할 수 있게 된다. 이는 버그와 보안 문제가 커질 가능성이 있다. 따라서 직렬화로 인해 발생하는 버그/보안 문제를 직렬화 프록시 패턴으로 해결할 수 있다.

 

1-1. 직렬화 프록시 패턴 구현하기

바깥 클래스의 논리적 상태를 정밀하게 표현하는 중첩 클래스를 설계해 private static으로 선언한다. 바깥 클래스와 직렬화 프록시 모두 implements Serializable 해야 한다.

  • 직렬화 프록시의 생성자 특징
    • 단 1개여야 한다
    • 바깥 클래스를 매개변수로 받아야 한다
    • 인수로 넘어온 인스턴스의 데이터를 복사하는 형태
    • 일관성 검사나 방어적 복사가 필요없다

 

1-2. 직렬화 프록시 패턴 예시

  • 직렬화 프록시 - SerializationProxy 클래스
public class SerializationProxy implements Serializable {
    private static final long serialVersionUID  = 234209470239740923L; // 아무 값
    private final Date start;
    private final Date end;

    public SerializationProxy(Period period) {
        this.start = period.start;
        this.end = period.end;
    }
    
    // Period.SerializationProxy 용 메서드 - readResolve()
    private Object readResolve() {
        return new Period(start, end);
    }
}
  • readResolve()
    • 바깥 클래스(Period)와 논리적으로 동일한 인스턴스를 반환하는 메서드
    • 직렬화 프록시 → 바깥 클래스 인스턴스 변환하는 역할이다
    • 생성자를 이용하지 않고도 역직렬화된 인스턴스 생성 기능을 제공한다
  • 바깥 클래스 - Period 클래스 
    • writeReplace() : 직렬화가 이뤄지기 전에 바깥 클래스의 인스턴스를 직렬화 프록시로 반환하는 메서드이다. 이로 인해 직렬화 시스템은 바깥 클래스의 직렬화된 인스턴스 생성이 불가능하다
    • readObject() : writeReplace 덕분에 바깥 클래스의 직렬화 인스턴스를 생성해낼 수 없지만, 바깥 클래스에 접근하려는 공격이 있을 수 있다. 이런 공격을 readObject()가 막아준다.
public class Period implements Serializable {
    // 접근자 어떻게 해야하나?
    final Date start;
    final Date end;

    public Period(Date start, Date end) {
        this.start = start;
        this.end = end;
    }

    // 직렬화 프록시 패턴용 메서드1
    private Object writeReplace() {
        return new SerializationProxy(this);
    }

    // 직렬화 프록시 패턴용 메서드2
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
        throw new InvalidObjectException("프록시가 필요합니다.");
    }
}

 

1-3. 직렬화 프록시 패턴 장점

  1. (방어적 복사와의 공통점) 가짜 바이트 스트림 공격과 내부 필드 탈취 공격을 프록시 수준에서 차단
  2. 직렬화 프록시는 final 필드로 선언 가능하기에 바깥 클래스를 진정한 불변으로 만들 수도 있다
  3. 필드의 직렬화 공격 걱정이 없다
  4. 역직렬화 할 때 유효성 검사 수행하지 않아도 됨
  5. 직렬화 인스턴스 클래스 ≠ 역직렬화 인스턴스 인 경우에도 정상 작동한다 (ex. EnumSet)

 

1-4. EnumSet - 5번 예시

이 클래스는 public 생성자 없이 정적 팩터리들만 제공한다. 이 클래스는 열거 타입의 원소 개수에 따라 다른 클래스를 사용하는데, 원소 개수가 64개 이하면 RegularEnumSet을, 65개 이상이면 JumboSet을 사용한다.

원소 64개짜리 열거 타입을 가진 EnumSet을 직렬화한 후(RegularEnumSet), 원소 5개를 추가해 69개의 원소를 가진 EnumSet(JumboSet)을 역직렬화 한다면 어떻게 될까?

  • 처음 직렬화된 인스턴스는 RegularEnumSet 인스턴스이다
  • 역직렬화는 JumboEnumSet 인스턴스가 반환된다
  • 이는 EnumSet이 직렬화 프록시 패턴을 사용했기 때문이다

 

2. 직렬화 프록시 패턴의 한계

  1. 클라이언트가 멋대로 확장할 수 있는 클래스에는 직렬화 프록시 패턴을 적용할 수 없다.
  2. 객체 그래프에 순환이 있는 클래스에도 적용할 수 없다. 이런 객체의 메서드를 직렬화 프록시의 readResolve 안에서 호출하려고 하면, ClassCastException이 발생할 것이다. 직렬화 프록시만 가졌을 뿐, 실제 객체는 아직 만들어진 것이 아니기 때문이다.
  3. 직렬화 프록시 패턴이 강력한 안정감을 주지만, 성능이 떨어질 수 있다.

요약

불변식을 지키기 위해 인스턴스 수를 통제(싱글턴 구현)해야 한다면, 열거 타입 사용을 권장한다.

직렬화 + 인스턴스 수 통제가 모두 필요하다면 readResolve() + 모든 참조 타입 인스턴스 필드 transient 선언을 하도록 하자.

  1. 열거타입
  2. readResolve() + 모든 인스턴스 필드 transient 선언

 

1. 싱글턴 패턴 + Serializable

싱글턴 패턴을 구현한 경우, implements Serializable로 직렬화를 구현했다면 이는 더이상 싱글턴처럼 동작하지 않는다.

커스텀 직렬화하고, readObject()를 제공하더라도 더이상 싱글톤으로 작동하지 않는다. 왜냐하면 클래스 초기화 시 생성한 인스턴스가 아닌 다른 인스턴스를 반환하기 때문이다.

 

 2. 해결책 - readResolve()

readResolve()를 사용하면 위의 경우에도 싱글턴 속성을 유지할 수 있다.

  • readResolve() psuedo code
readResolve(역직렬화 후 새로 생성된 객체) {
	return 직렬화한 객체 참조;
}

 

  • readResolve() - 예시
// 인스턴스 통제를 위한 readResolve - readObject()로 인한 문제 해결 가능성 존재
private Object readResolve(매개변수) {
  // 진짜 Elvis를 반환하고, 가짜 Elvis는 가비지 컬렉터에 맡긴다. 
  return INSTANCE;
}
  • 이 readResolve()는 역직렬화한 객체는 무시하고 클래스 초기화 때 만들어진 Elvis 인스턴스를 반환한다. 이때 Elvis의 인스턴스의 모든 필드를 transient로 선언해야 한다.

 

2-1. transient 사용

readResolve()를 인스턴스 통제 목적으로 사용한다면, 객체 참조 타입 인스턴스 필드는 모두 transient로 선언해야 한다. 그렇지 않으면 역직렬화된 객체의 참조 공격의 가능성이 있다.

 

2-2. 객체 참조 공격

역직렬화할 인스턴스에 non-transient 필드가 존재한다고 가정해보자. 이 경우에는 필드 내용이 역직렬화 된 후에 readResolve()가 실행된다. 이렇게 필드 내용이 역직렬화되는 시점& readResolve()가 실행되기 전 에 잘 조작된 스트림을 사용해 역직렬화된 인스턴스를 훔쳐올 수 있다.

 

  • 공격 원리
[도둑 클래스]
- 필드(impersonator) : 훔칠 예정인 싱글턴 직렬화 참조를 저장하는 역할

[싱글턴 클래스]
- non-transient 불변 필드(INSTANCE) : 도둑의 인스턴스 참조가 저장됨

* 싱글턴이 도둑 클래스의 참조를 포함하므로 싱글턴이 역직렬화될 때 도둑의 readResolve()가 먼저 호출됨(Elvis의 readResolve()가 아닌)
* 도둑의 readResolve()가 수행될 때,
  도둑의 인스턴스 필드에는 역직렬화 중인 싱글턴의 readResolve()가 수행되기 전의 싱글턴 참조가 담긴다

 

  • steal 시점
(1) 역직렬화 (2) ——————> (3) readResolve() 실행(싱글턴 객체 반환)

(2)번 단계에서 instance steal이 발생할 수 있다.

 

  • 잘못된 싱글턴
    • transient가 아닌 참조 필드를 가지고 있다
    • Elvis가 역직렬화 될 때, INSTANCE에 담긴 도둑 클래스 참조로 인해 Elvis의 readResolve()가 아닌, ElvisStealer의 readResolve()가 실행된다
    public class Elvis implements Serializable {
        // transient가 아닌 참조 필드
        // transient가 아니기에 이후 도둑의 인스턴스 참조가 담길 예정
        public static final Elvis INSTANCE = new Elvis();
        private Elvis() {}
        
        private String[] favoriteSongs = { "Hound Dog", "HeartBreak Hotel" };
        
        public void printFavorites() {
            System.out.println(Arrays.toString(favoriteSongs));
        }
        
        private Object readResolve() {
            return INSTANCE;
        }
    }
    

 

  • 도둑 클래스
public class ElvisStealer implements Serializable {
    static Elvis impersonator; // 훔칠 Elvis 인스턴스의 참조를 저장할 필드
    private Elvis payload; // 기존의 싱글턴 Elvis를 받음
    
    // .. ElvisStealer 생성자 
    ElvisStealer(Elvis elvis) {
        this.payload = payload;
    }
    
    private Object readResolve() {
      // resolve 되기 전의 Elvis 인스턴스의 참조를 저장한다.
      impersonator = payload;
      
      // favoriteSongs 필드에 맞는 타입의 객체를 반환한다.
      return new String[] {"A Fool Such as I"};
    }
    
    private static final long serialVersinUID = 0;
}
    

 

  • 아래 코드를 실행하면 서로 다른 2개의 Elvis 인스턴스를 생성한다
public class ElvisImpersonator {
    // 진짜 Elvis 인스턴스로는 만들어질 수 없는 바이트 스트림
    private static final byte[] serializedForm = {
            (byte)0xac, (byte) 0xed, 0x00, 0x05, 0x73, 0x72, 0x00, 0x05,
            0x45, 0x6c, 0x76, 0x69, 0x73, (byte)0x84, (byte) 0xe6,
            (byte)0x93, 0x33, (byte)0xc3, (byte)0xf4, (byte)0x86
            // .. 
    };

    public static void main(String[] args) {
        // ElvisStealer.impersonator 를 초기화한 다음
        // 진짜 Elvis(즉, Elvis.INSTANCE)를 반환한다.
        Elvis elvis = (Elvis) deserialize(serializedForm);
        Elvis impersonator = ElvisStealer.impersonator;
        
        elvis.printFavorites();
        impersonator.printFavorites();
    }
}

 

  • 결과
    • Elvis는 싱글턴 패턴으로 구현했음에도 두 개의 인스턴스가 생성된 것을 확인할 수 있다.
    • 따라서, 싱글턴인 경우에도 implements Serializable을 한다면 싱글턴이 깨질 수 있다
[Hound Dog, Heartbreak Hotel]
[A Fool Such as I]

→ 싱글턴 + Serializable + readResolve()를 설정했더라도, non-transient 필드가 있다면 싱글턴이 깨진다.

 

2-3. 해결방법

위처럼 싱글턴 패턴인데도 직렬화 때문에 2개의 인스턴스가 생성되는 해결법으로는 2가지가 있는데, 다음과 같다.

  1. 인스턴스 필드 transient 선언 (transient favoriteSongs)
    1. 이 방법은 readResolve()를 사용해 순간적으로 만들어진 역직렬화된 인스턴스에 접근하지 못하는 로직을 구현해야하고, 이는 깨지기 쉽고 신경을 많이 써야해서 권장하지 않는다.
  2. Elvis를 원소 하나짜리 열거 타입으로 변경
    1. 저자는 이 방법을 더 권장!
    2. 직렬화 가능 인스턴스 통제 클래스 + 열거 타입으로 구현하면 선언한 상수 객체만 존재함을 자바가 보장한다

 

3. 열거 타입 싱글턴

열거 타입으로 싱글턴은 구현한 코드는 다음과 같다.

public enum EnumElvis {
    INSTANCE;

    private String[] favoriteSongs = { "Hound Dog", "Heartbreak Hotel" };
    
    public void printFavorite() {
        System.out.println(Arrays.toString(favoriteSongs));
    }
}

열거 타입으로 싱글턴을 구현할 수 있지만, 모든 경우에 적용되는 것은 아니다. 만약 컴파일 타임에 어떤 인스턴스가 있는지 알기 어렵다면, 열거타입을 적용하기 어렵다. 이때는 readResolve로 직렬화한 인스턴스를 통제하면 된다.

 

4. readResolve() 접근성

  • final 클래스
    • private readResolve()
  • non-final 클래스
    • private readResolve() : 하위 클래스에서 사용 불가능
    • package-private readResolve() : 같은 패키지, 하위 클래스에서만 사용 가능
    • protected or public readResolve() : 재정의하지 않은 모든 하위 클래스에서 사용 가능하다
      • 다만, 재정의하지 않은 하위클래스를 역직렬화할 경우 상위 인스턴스 생성시 ClassCastException이 발생할 수 있다

재작년 첫회사를 그만두고 6개월이 지난 뒤 작년 지인소개로 면접을 본 회사에 최종합격해서 2024.06.03부터 현회사에 근무하기 시작했다. 이번달로 이 회사에서 근무기간 만 1년을 채웠는데, 다달이 보면 꽤 긴 시간이었던 것 같기도 하지만 벌써 1년이 지났다는 것에 약간 놀라기도 했다. 그 기간동안 많이 배우기도 하고, 크고 작은 변화들도 있었는데 1년이 지나고 나서 할 수 있는 얘기들이 있을 것 같아 기록해보려 한다.

 

[감정변화]

첫 근무를 시작하고 난 뒤 나를 지배했던 감정은 불안과 두려움이었다. 전회사에서의 경험이 썩 유쾌하지는 않았던 터라

‘나보고 속도 느리다고 지적하면 어떡하지?’ 

‘일 못한다고 권고사직하면 어떡하지?’

‘다른 직원들만 편애하고 나를 홀대하면 어떡하지?’ 

와 같은 경험에 기반한 생각들이 나를 괴롭혔다. 주어진 업무를 할 때 코드가 잘 작성이 안되거나 구현에 시간이 좀 걸릴때마다 이 불안은 튀어나왔다. 그럴때마다 드는 감정을 있는 그대로 일기장에 쓰고, ‘여기는 전회사가 아니야’라고 되뇌었다. 이것들을 느리지만 꾸준히 했기에 효과가 있었고, 회사에 들어온지 10개월이 지난 후부터는 이로부터 거의 다 벗어난 것 같다. 더이상 지난 과거가 아프지 않고, 비슷한 상황이 와도 이전과 같은 반응을 하지 않는다. 

 

이전의 경험을 반면교사한 것도 있고, 다짐한 것도 있지만 일련의 과정으로 내 마음을 돌보면서 내 마음이 안정되고 편안해지니 업무가 더이상 고역이 아니게 되었고 회사에서 업무를 적절히 수행하는데 집중할 수 있게 되었다.(물론 힘든 순간은 매일 있지만) 그리고 다행히도 현회사는 신입이라고 이해해주는 회사였고, 근속년수가 평균 10년, 최대 20년 이상인 분들이 있어서 근속 1년을 채울 수 있었던 것 같다.

 

일을 할 때 그저 기술만 잘 익히고 사회생활만 잘 하면 된다고 생각했었는데, 그 전에 자기의 감정/심리 상태가 안정되어있는게 선행되어야한다는 걸 느꼈던 경험이었다. 질문하면 나를 무시할까봐, 질문했을 때 친절한 답변을 듣지 못할까봐, 대화가 안 통하는 것이 두려운 상태일 때는 꼭 질문을 하고 소통을 해야하는 상황임에도 회피하게 된다. 그게 잘못이라기보다 내 마음 상태가 그만큼 안정되어있지 않으면 업무 하나를 수행하는데도 많은 에너지가 들게되고 쉽게 지치고, 회사가 ,업무가, 사람들이 버거워지게 된다. 이건 스스로에게도 좋은 일은 아니라는 것을 경험을 통해 알게 되었기에, 계속해서 스스로를 돌보려고 노력 중이다.

 

[개발]

전회사는 이커머스 스타트업이었고, 현회사는 중소 금융SI 회사로 회사 성격과 규모도 다르고 사용하는 기술 스택도 다르다. 개인적으로는 현회사 기술스택이 나와 더 잘 맞는 느낌이다. 

 

전회사에서는 php에 vue.js로 화면단에서 서버까지 모두 구현했다면, 현회사는 화면단 프레임워크는 다르게 사용하지만, 서버는 기본적으로 자바를 사용해 구현하기 때문에 이전에 습득한 기술과 더 일치하는 부분이 있어 나로서는 일관성이 있는 느낌이었다. 또 전회사는 개발인원이 2,3명으로 굉장히 작고 마케팅 같은 다른 직무의 백업을 해주는 역할이라 개발이 주가 아닌 느낌이었고 데이터 전송할 때 DTO 사용 안하고 소프트웨어 구조 짜는 것도 구조적이지 못한 느낌이 있었다면, 현회사는 아예 금융SI이기 때문에 회사 직원 대부분이 개발자이고, 소프트웨어 상품을 만들어서 유통하는게 주업무이다 보니 개발적으로 소통하거나 업무를 함에 있어 나에게 더 잘 맞는 환경이다. 곁다리 개발이 아닌 메인 개발을 하는 느낌.

 

하지만 아쉬운 부분도 있는데, 개발 회사이기는 하지만 SI이고, B2B 방식의 비즈니스이기 때문에 

B2C처럼 사용자와 맞닿은 서비스를 개발하지 못하는 것,

기술스택이 옛날에 머물러 있고 최신 기술을 잘 적용하지 않는다는 것,

개발적인 지식보다는 금융 도메인 지식이 더 중요한 분야인 것,

프론트엔드, 백엔드 구분이 있지 않고 화면을 맡으면 화면단부터 서버까지 다 개발해야하는 것,

SI회사 특성상 수주사의 눈치를 보느라 내 기준 이해가지 않는 부분들을 개발해야하는 것과 업무가 많아 야근이 잦다는 것, 프로젝트에 따라 계속해서 근무 위치가 변동하는 것,

업무가 많다는 이유로 휴가를 사용하지 못하게 한다는 것과 야근/주말출근을 당연하게 생각하는 분위기

 

가 있다. 그런데 장점만 있는 곳은 없기에 장점과 같이 단점도 수용하려고 노력하는 중이다. 그리고 솔직히 말하면 현 경제상황이 안 좋기 때문에 이렇게 일하고 돈을 벌 수 있다는 것에 감사함을 느끼는 게 더 크다. 열심히 일해서 돈 벌어야지.

 

다행히도 원래 화면 구현하는 능력은 취직하기 전에 거의 0에 가까운 수준으로 무지했었는데, 전회사에서 몇 번 화면을 구현하면서 기술을 좀 터득한 것들이 현회사 업무하는데 도움이 되었다.기본적으로는 html, css 부터 자바스크립트까지. 여기에 문서 찾아보고 그거 기반으로 개발하는 것도 도움이 많이 되었다. 그래서 그런가 현회사에서 개발하면 어려운 적은 많았지만 전회사에서처럼 그 어려움에 압도되지는 않았던 것 같기도 하다.

 

 

[사회생활]

 

  • 회사에서 자아를 버리는 연습

사실 현회사에서 가장 크게 얻은 것은 연봉도, 기술스택도 아닌 바로 회사생활에서의 마음가짐이다. 회사에서 자아를 버리는 연습을 정말 많이 했다. 내가 느끼기에 나는 자아가 좀 세다고 느끼는 사람인데, 이게 일상이 아닌 회사에서 자아가 튀어나오면 무엇보다 본인이 힘들다는 것을 알게 되었다. 

회사는 기본적으로 돈이라는 이해관계로 굴러가는 곳이고, 돈을 벌기 위해 모인 사람들이기에 돈과 고객을 중심으로 돌아가야 하는 곳이다. 회사는 자아실현을 하는 곳이 아니다.

그런데 이런곳에 자아가 개입하게 되면 나도, 상대방도 힘들어지는 것을 느꼈다. 예를 들어 회사에서 돈을 벌기 위해 그들의 입장에서 합리적으로 시킨 일을 내가 마음에 안들고 하기 싫다는 이유로 안하게 되면 그들은 나를 고용한 이유가 없어진다. 그러므로 회사의 입장에서 그들이 돈을 벌기 위해 나에게 시킨 일을 자아를 버리고 해내는 것이 나에게 필요하다는 것을 알고 체득하기 위해 노력했다. (물론 도덕적으로 어긋나는 일은 예외겠지만)

내가 애정하는 일, 회사가 아니라면 그냥 나는 회사에서 시키는 일을 잘 수행하기 위해 생각하고, 행동하면 된다는 것을 알게 되었다. 그 시작은 회사 내부 인테리어로 전직원을 내부 청소를 시키고, 가구를 나르는 일을 

시켰었는데, 그때 나는 물건 정리하고, 바닥 먼지 닦고, 물건 옮기는 일을 했었는데 내가 이걸 왜 해야하나 욕이 나올 정도였는데, 그냥 했다. 싫으면 다른 회사 가면 되고, 회사에서 시키는 일들을 해야하는 거니까.

이걸 시작으로 업무에서도 자아를 빼고자 했다.

 

  • 일은 내 중심이 아닌 타인 중심

위에서도 말했지만 회사는 자아실현을 하는 곳이 아니고, 나의 노동력을 제공한 대가로 돈을 받는 철저히 이해관계에 기반한 곳이다. 그렇기에 내가 제공하는 노동력이 쓰임이 있어야 하고, 그 쓰임은 내가 제공하는 것과 타인이 원하는 것이 일치할 때 생기는 것 같다. 돈을 주는 고객 입장에서 돈을 지불한 만큼의 가치를 제공해야, 즉 돈값을 해야 고객은 만족하고 그 계약이 성공적으로 마무리되었다고 할 수 있다. 또 추후에 같은 고객이 의뢰를 해올 수 있고 미래의 계약도 확보할 수 있지 않을까. 그만큼 일을 함에 있어 타인의 입장, 즉 고객의 입장을 잘 파악하고 그에 맞추어 적절한 것을 제공할 수 있는 것이 중요하다는 것을 알게 되었다.

 

아직 말단 사원이라 영향력도 없고, 할 수 있는 것도 많이 있지는 않지만, 그냥 내가 지금 하고 있는 일, 업무 화면을 만듦에 있어서도 고객의 성향을 맞추고, 사용자 입장에서 납득가능하고, 사용하기 편한 화면을 개발하는 것도 이에 해당한다고 생각한다. 이에 기반해 더 많은 생각과 행동, 경험을 통해 일을 대하는 생각과 가치관, 경험이 깊어졌으면 한다. 이게 어쩌면 회사에서 자아를 버려야하는 가장 큰 이유일수도..?

 

  • 더이상 인정을 갈구하려 노력하지 않는다

전회사에서 가장 힘들었던 것이 인정을 미친듯이 갈구했던 것이다. 그때는 회사에서 자아실현을 할 수 있다고 믿었고, 일을 잘해서 인정받고 싶은 욕구가 지나치다보니 타인의 말한마디, 평가 하나하나에 목 메었던 것 같다. 

칭찬 하나를 들으면 기분이 들떴고, 비판을 받거나 코드나 업무 지적을 받으면 마음에 화살이 꽂힌 것처럼 아팠다. 타인의 평가에 민감하다보니 항상 긴장과 불안도가 높았고, 몸과 생각은 경직되었다. 그렇게 지내다 결국 안좋은 결말을 맞이하고, 스스로 무너짐과 재건을 반복하고, 내면에서 하는 말과 욕망에 귀를 기울이며 스스로에게 애정을 주다보니 이제는 타인의 인정을 갈구하지 않게 되었다. 외부 세계와의 상호작용은 필수적인 것이지만, 그만큼 나자신과의 내부 세계와의 상호작용도 중요하다는 것을 알게되었다. 스스로를 온전히 받아들이고, 스스로가 느끼고 생각하는 것을 잘 들어주는 것이 나자신을 친절하게 대하는 방법이고 오롯이 잘 서있을 수 있는 방법이라는 것을 알게 되었다. 또한 회사라는 곳이 어떤 곳인지 알게 되는 것도 영향을 주었던 것 같다. 회사는 사랑받으러 가는 곳이 아닌, 내 노동력을 제공하고 돈을 받는 곳이라는 걸 알게 되니까 회사에서 인정과 친밀감을 받지 못해도 괜찮아졌다.

 

  • 타인을 내 마음/일상에서 작게 만드는 연습

전회사에서 스스로 미숙했다고 느꼈던 부분이 퇴근을 하고 나서도 주변 사람들한테 일 얘기를 했던 것이다. 평일 퇴근을 한 후에는 엄마랑 산책하면서 그날 있었던 선임이 싸질러 놓았던 말들과 행동들을 열거하면서 욕하기도, 스스로를 탓하기를 반복했다. 심지어 운 적도 있다. 퇴근 후, 주말은 온전한 내 시간이기에 나와 내가 사랑하는 사람들에게 집중해도 모자랄 시간에 나와 크게 상관없는 사람 얘기를 하면서 내 마음에 타인을 들여놓고, 마음을 계속 이염시켰던 것 같다. 그러니 내 감정, 신체, 일상을 돌볼 여력이 없고, 압박감과 스트레스는 점점 커져갔다. 내 중심은 점점 사라져 가고, 타인에 휘둘리는 시간을 겪으며 고통스러움을 느끼다 운좋게도(?)  타의로 그 상황에서 벗어난 뒤 그때의 내 행동이 누구보다 나 자신에게 안 좋았던 것을 뒤늦게 알았다. 

 

나의 시간과 에너지는 한정되어있기에 이를 나에게 이로운 방향으로 써야하는데, 애먼 곳에 사용한 격이었다. 내 시간과 에너지를 나에게 쓰면 내 스스로가 마음에 드는 사람으로 변할 가능성이 높아지지만, 내 시간과 에너지를 타인에게 지나치게 쓰다보면 나 스스로 가꿀 시간은 줄어들고 계속 변하고 확실하지 않는 타인의 반응과 인정에 휘둘리게 된다. 사실 이런 성향은 현회사에 입사하고 나서도 꽤 오랫동안 유지되었던 성향이었고, 아직도 이로부터 완전히 자유롭다고 할 수는 없지만, 타인을 무분별하게 나의 영역에 들였을 때의 괴로움을 너무나도 잘 알고 있는 터라 이제는 더이상 그러지 않으려고 한다. 사회생활을 하며 타인과 소통은 하되, 그들을 내 마음에, 생각에 들이지 않는 것. 이게 스스로를 지킬 수 있고 사회의 면역력을 키우는 방법이 아닐까 라고 생각한다.

 

[사내문화]

  • 복장

사실 전회사와 극명하게 다른 부분이 사내문화 같은데, 정말 정반대의 환경(복장, 휴가, 사내 분위기)을 경험했다. 

복장은 전회사는 슬리퍼 신고 반바지 입고 심지어 C레벨도 캡모자를 쓰고 출근을 하는 환경이었는데, 현회사는 입사확정되었을 때 복장 규정 pdf가 이메일로 날아올 정도로 복장에 비교적 엄격한 편이다.

캡모자/반바지 절대 안되고, 청바지도 안된다. 비즈니스캐쥬얼로 블라우스에 슬랙스, 스니커즈 정도만 허용한다. 이또한 젊은 직원들 대상인 것 같고, 수석이나 상무 이상 직급들은 거의 와이셔츠에 정장바지 구두가 기본값인 것 같다.

처음엔 진짜 적응 안되고 지금도 안되지만,, 어쩔 수 없다.

 

  • 휴가

또 가장 다른 문화는 휴가이다. 사실 이것 때문에 이직을 생각할 정도이다. 전회사 현회사 모두 입사 1년 이내에는 월에 1개씩 휴가가 생겼는데, 전회사는 상황 괜찮으면 몰아서 일주일치 휴가도 쓸 수 있었고 미래의 휴가를 미리 당겨쓸 수도 있고 휴가신청도 허락같은 거 안 받고 휴가 전날까지에만 1차 결재자, 2차 결재자 flex에 등록해서 올리면 끝이었다. 근데 여기는 몰아서 휴가 신청하는 것도 하지 말라고 약간의 압박?을 주고 월에 최대 1개까지만 쓰라고 했으며 휴가를 쓰려면 1차 결재자, 2차 결재자한테 일주일 전에 허락을 받고 결재를 올릴 수 있다. 그리고 일해야지 휴가를 왜 써? 이런 분위기도 좀 있는 것 같다. 처음에는 이런 분위기에 굉장히 불만이었다. 그런데 다니다보니 적응이 되기도 했고, 일이 정말 바빠져서 휴가를 쓸 수 있는 여유가 없어서 많이 못 썼다. 하지만 마음 속에서는 이게 맞나? 싶은 생각이 드는 것도 사실이다. 1년에는 이정도는 쉬어야 한다고 나라에서 법적으로 보장된 휴가가 있는데 왜 회사가 이에 제재를 거는지 지금도 이해가 가지 않는다. 게다가 병가도 없다. 이처럼 휴가에 제약이 있고, 휴가 승인을 위한 장벽이 있으면 정말 휴가를 써야할 상황에서도 망설이게 된다. 천재지변으로 출근을 못하게 될 경우, 정말 아파서 일을 할 상황이 아닌 경우에도 휴가 쓰기가 망설여지고, 그 상황을 뚫고 일하러 가야한다는 부담과 압박감이 느껴진다. 휴가도 업무 환경을 조성하는 것이라고 생각하는데, 이게 잘 보장되지 않으니 불만족스러운 부분이 있다.

 

  • 사내분위기

사내분위기도 극과극을 경험한 것 같은데, 전회사는 내 또래의 동료분들이 많았다면, 현회사는 내 또래의 직원이 거의 없고 대부분 40대 이상의 기성 남성 중심으로 구성되어있다. 구성원이 어떤 사람들로 구성되어있는지는 큰 영향을 주는 것 같다. 전회사에서는 직원들과 친해져서 점심/저녁도 같이 먹고, 업무 중간에 과자 먹으면서 수다 떠는 시간도 많았던 반면 현재는 거의 업무 얘기 빼고는 사적인 얘기 거의 안하고, 친해진 사원들도 몇 없다. 처음엔 이 상황이 외롭고 고립되고 심심한 느낌이 생경했지만, 적응 되기도 했고 사실 아직도 조금씩 이 감정들을 느끼기는 하지만 이제는 어쩔 수 없다 하고 있다. 확실히 회사에서 수다 떨고 업무 얘기도 자유롭게 얘기할 사람들이 있다는 게 좀 큰 것 같기도 하다.

 

또 막내라는 이유로 업무외 잡일을 시키는 것이 자연스러운 분위기이다. 이건 어떤 사람이냐에 따라 다르긴 한데, 특정 사람들은 말단 사원에게 사적인 일들을 시키는 것을 자연스럽게 생각한다. 커피머신 청소 시키는 거나, 정수기 물통 주문하기, 자기 회식 때 쓸 카드 사오게 하기, 사무실 비품(과자, 문구류) 사오게 시키기 등. 아직도 생각하면 빡치기는 한데 절이 싫으면 중이 떠나는 수밖에. 어쩔 수 없다.

 

[그 외]

  • 연봉

첫회사에서 만족스럽지 않은 것 중 하나가 연봉이었는데, 현회사로 이직하면서 약 20%, 올해의 연봉협상으로는 8%가 올랐다. 전회사에서는 연봉이 적으니 하는 일에 진심을 다하지 않게 되고(?ㅋㅋ) 잔잔하게 손해보는 느낌이 깔려있는데, 현회사를 통해 연봉이 오르니 이 부분은 만족스럽다. 또 설/추석 상여금도 현금으로 많이 준다. 돈이 회사생활의 전부라고는 할 수 없지만, 확실히 회사는 돈으로 굴러가는 집단이다보니 돈이 중요한 부분을 차지하는 것이 맞는 것 같다. 일을 하고, 금전적으로 그만한 대우를 받는게 일을하는 동기에 있어서 중요하다는 걸 알게 되었다.

 

  • 운전

현회사 다니면서 단연 가장 큰 변화는 운전이라고 할 수 있다. 본가에서 회사까지가 거리가 꽤 되었던 터라 운전의 필요성을 절감했고, 운전을 굉장히 무서워함에도 작년 8월부터 장롱면허 타파를 위해 운전연습을 계속 해왔다. 왕복 4시간의 대중교통 출퇴근은 도저히 할 수 없을 거라 판단했었다. 그 결과 현재는 다른 위치로 출퇴근하긴 하지만 차로 출퇴근을 하고 있고, 대중교통으로 1시간 이상 걸리는 것에서 자차로 편도 30분으로 출퇴근 하고 있다. 아직도 쫄보이고 운전을 못하지만,, 너무도 두려워하던 것을 이겨내고 하게 되었을 때 묘한 뿌듯함이 있는 것 같다. 평생 앞으로 운전 할 일은 없을 거라고 여겼었는데 차로 출퇴근을 하다니 참 인생 모를 일이다.

 

[만족하는 점]

지난 1년을 회고하는 글을 쓰며 만족하는 점과 아쉬운 점이 명확하게 보이는데, 만족하는 점으로는 

연봉 상승하고, 

개발 업무 경력이 쌓인 것, 

내가 하고싶은 거 하고 사고싶은 거를 살 수 있고 부모님께 의존하지 않는 경제적 안정감, 

퇴근 후나 주말에 내가 하고 싶은 거를 취준 신경 안쓰고 할 수 있는 시간적 안정감, 

운전 배운 것, 

사회생활을 배우고 심리적 안정감

을 쌓아온 것이 있을 것 같다.

 

[아쉬운 점]

아쉬운 점으로는 개발 실력면에서는 크게 향상되지 못한 점, 

휴가를 제대로 쓰지 못한 점(못쓰고 돈으로 환급받지도 못하고 날린 휴가가 6일 정도 있다), 

이직이나 해외취업을 생각하고 있는데 이를 위한 준비를 많이 못한 것 

정도가 있을 것 같다.

 

좋아진 점들도 많지만, 앞으로는 개발적으로도 실력을 키우는데 집중해서 좋은 사람들이 있고 좋은 환경에서 일할 있는 곳으로 이직할 있기를 바라며 1 회고 .

+ Recent posts